Design Documents
(From https://docs.couchdb.org/en/stable/ddocs/index.html)
CouchDB supports special documents within databases known as design documents. These documents, mostly driven by JavaScript you write, contain all of the database logic and are used to build indexes, validate document updates, format query results, and filter replications.
They are written in JavaScript and are executed within the CouchDB application.
Design documents are basically just like any other document within a CouchDB database; that is, a JSON structure that you can create and update using the same PUT
and POST
HTTP operations. The difference is that they are located within a special area of each database (_design), and they are processed and accessed differently when you want to actually execute the functional components.
Importantly, any database can have zero or more design documents. There are no limits to the number of design documents that you have, although from a practical perspective it's unlikely that you'll have more than ten for most databases, especially since for many of the design document components you can have more than one definition in each design document.
structure of a Design Document
Design documents contain functions such as view and update functions. These functions are executed when requested.
Design documents are denoted by an id field with the format _design/name
. Their structure follows the example below.
{ "_id": "_design/example", "views": { "view-number-one": { "map": "function (doc) {/* function code here - see below */}" }, "view-number-two": { "map": "function (doc) {/* function code here - see below */}", "reduce": "function (keys, values, rereduce) {/* function code here - see below */}" } }, "updates": { "updatefun1": "function(doc,req) {/* function code here - see below */}", "updatefun2": "function(doc,req) {/* function code here - see below */}" }, "filters": { "filterfunction1": "function(doc, req){ /* function code here - see below */ }" }, "validate_doc_update": "function(newDoc, oldDoc, userCtx, secObj) { /* function code here - see below */ }", "language": "javascript" }
As you can see, a design document can include multiple functions of the same type. The example defines two views, both of which have a map function and one of which has a reduce function. It also defines two update functions and one filter function. The validate_doc_update
function is a special case, as each design document cannot contain more than one of those.
The Components of a Design Document
A design document provides the following:
- Views
- Views are functions that take your document data and produce searchable lists of information based on the document's contents. Views are incredibly simple yet also very powerful and provide the main route of entry into your data for information for which you don't know the document ID.
- Shows
- A show converts a single document into another format, usually HTML, although you can output the document in any format, including JSON or XML, to suit your application. Shows can also be used to simplify the JSON content of a document into a simpler or reduced format.
- Lists
- A list is to a view (or collection of documents) as a show is to a single document. You can use a list to format the view as an HTML table, or a page of information or as XML of your document collection. In this way, a list acts as a transform on the entire content on the view into another format.
- Document validation
- Because you can store any JSON document in your database, one of the issues is maintaining consistency. This is what the document validation functions solve. When you save a document into CouchDB, the validation function is called, and it can either check or even reformat the incoming document to meet your requirements and standards for different documents.
- Update handlers
- Update handlers can be used to perform an action on a document when the document is updated. Unlike document validation, update handlers are explicitly called, but they can be used to make changes to a document within the server without having to retrieve the document, change it, and save it back (as would be required for a client process). For example, you can use update handlers to increment values in a document, or add and update timestamps.
- Filters
- When exchanging information between CouchDB databases when using replication or the changes feed, you may want to filter the content of the database. For example, if you store information about your CD and DVD collection in a single database, you may want to exchange only the CD records with another database. This is what a filter function is for: when called, it examines the list of supplied documents from the replication or changes feed and then either returns the document or null.
View Functions
Views are the primary tool used for querying and reporting on CouchDB databases.
>Map Functions
Map functions accept a single document as the argument and (optionally) emit()
key/value pairs that are stored in a view.
An example:
function (doc) { if (doc.type === 'post' && doc.tags && Array.isArray(doc.tags)) { doc.tags.forEach(function (tag) { emit(tag.toLowerCase(), 1); }); } }
In this example a key/value pair is emitted for each value in the tags array of a document with a type of post
. Note that emit()
may be called many times for a single document, so the same document may be available by several different keys.
Also keep in mind that each document is sealed to prevent the situation where one map function changes document state and another receives a modified version.
For efficiency reasons, documents are passed to a group of map functions - each document is processed by a group of map functions from all views of the related design document. This means that if you trigger an index update for one view in the design document, all others will get updated too.
Since version 1.1.0, map supports CommonJS modules and the require()
function.
Reduce and Rereduce Functions
Reduce functions take two required arguments of keys and values lists - the result of the related map function - and an optional third value which indicates if rereduce mode is active or not. Rereduce is used for additional reduce values list, so when it is true there is no information about related keys (first argument is null
).
Note that if the result of a reduce function is longer than the initial values list then a Query Server error will be raised. However, this behavior can be disabled by setting reduce_limit config option to false:
[query_server_config] reduce_limit = false
While disabling reduce_limit might be useful for debug proposes, remember that the main task of reduce functions is to reduce the mapped result, not to make it bigger. Generally, your reduce function should converge rapidly to a single value - which could be an array or similar object.
Built-in Reduce Functions
Additionally, CouchDB has a set of built-in reduce functions. These are implemented in Erlang and run inside CouchDB, so they are much faster than the equivalent JavaScript functions.
Show Functions*
List Functions*
Update Functions
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.
A basic example that demonstrates all use-cases of update handlers:
function(doc, req) { if (!doc) { if ('id' in req && req['id']) { // 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!'] }
...
Filter Functions
Filter functions mostly act like Show Functions and List Functions: they format, or filter the changes feed.
Classic Filters
By default the changes feed emits all database documents changes. But if you're waiting for some special changes, processing all documents is inefficient.
Filters are special design document functions that allow the changes feed to emit only specific documents that pass filter rules.
Let's assume that our database is a mailbox and we need to handle only new mail events (documents with the status: new). Our filter function would look like this:
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
if a document passed all the rules. Now, if you apply this function to the changes feed it will emit only changes about new mails
:
GET /somedatabase/_changes?filter=mailbox/new_mail HTTP/1.1
We probably need to filter the changes feed of our mailbox by more than a single status value. We're also interested in statuses like “spam” to update spam-filter heuristic rules, “outgoing” to let a mail daemon actually send mails, and so on. Creating a lot of similar functions that actually do similar work isn't good idea - so we need a dynamic filter.
You may have noticed that filter functions take a second argument named request. This allows the creation of dynamic filters based on query parameters, user context and more.
The dynamic version of our filter looks like this:
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 passed the status query parameter in the request to let our filter match only the required documents:
GET /somedatabase/_changes?filter=mailbox/by_status&status=new HTTP/1.1>
and we can easily change filter behavior with:
GET /somedatabase/_changes?filter=mailbox/by_status&status=spam HTTP/1.1
Combining filters with a continuous feed allows creating powerful event-driven systems.
View Filters
Request Objects*
...