MapReduce Views
Edit this article in GitHub
Version 2.4

MapReduce Views

You can use MapReduce Views to create queryable secondary indexes in Couchbase Server.

The normal CRUD methods allow you to look up a document by its ID. A MapReduce (view query) allows you to look up one or more documents based on various criteria. MapReduce views are comprised of a map function that is executed once per document (this is done incrementally, so this is not run each time you query the view) and an optional reduce function that performs aggregation on the results of the map function. The map and reduce functions are stored on the server and written in JavaScript.

MapReduce queries can be further customized during query time to allow only a subset (or range) of the data to be returned.

Tip: See the Incremental MapReduce Views and Querying Data with Views sections of the general documentation to learn more about views and their architecture.

The following example is the definition of a by_name view in a "beer" design document. This view checks whether a document is a beer and has a name. If it does, it emits the beer's name into the index. This view allows beers to be queried for by name. For example, it's now possible to ask the question "What beers start with A?"

function (doc, meta) {
    if (doc.type && doc.type == "beer" && doc.name) {
        emit(doc.name, null);
    }
}

Querying Views through the C SDK

In the C client library, query results are delivered on a per-row basis to a given callback, which is specified at query time.

Querying a view is performed through the lcb_view_query() function. To use this function, include the <libcouchbase/views.h> file.

First, a handler function is defined to deal with each row (and at the end, any metadata) of the query:

Per-Row callback
static void viewCallback(lcb_t instance, int ign, const lcb_RESPVIEWQUERY *rv) {
    if (rv->rflags & LCB_RESP_F_FINAL) {
        printf("*** META FROM VIEWS ***\n");
        fprintf(stderr, "%.*s\n", (int) rv->nvalue, rv->value);
        return;
    }

    printf("Got row callback from LCB: RC=0x%X, DOCID=%.*s. KEY=%.*s\n", rv->rc,
        (int) rv->ndocid, rv->docid, (int) rv->nkey, rv->key);

    if (rv->docresp) {
        printf("   Document for response. RC=0x%X. CAS=0x%llx\n",
            rv->docresp->rc, rv->docresp->cas);
    }
}
This callback is invoked for each row. The end of the query (when there are no more rows to return) is signaled by having the LCB_RESP_F_FINAL bit set in the response's rflags field.

The response structure contains a key and nkey, value and nvalue, and docid and ndodic fields that contain the buffer and lengths for the emitted keys and values of the view result and the corresponding document ID. The final row (the one with the LCB_RESP_F_FINAL flag set) contains the "shell" of the response (that is, any errors, debug information, and the total_rows field) in the value field.

All the fields (except docid) are JSON encoded and should be decoded by a JSON decoder before use.

To actually invoke the query, populate the request structure:
lcb_CMDVIEWQUERY vq = { 0 };
lcb_view_query_initcmd(&vq, "beer", "by_name", NULL, viewCallback);
lcb_error_t rc = lcb_view_query(instance, NULL, &vq);
if (rc != LCB_SUCCESS) {
    // Handle error
}
lcb_wait(instance);
The lcb_view_query_initcmd() convenience function allows you to populate the command structure with common parameters. Unlike most library operations, the callback here is specific to the operation rather than global.

Of course, the command structure can also be populated manually:

lcb_CMDVIEWQUERY vq = { 0 };
vq.ddoc = "beer";
vq.nddoc = strlen(vq.ddoc);
vq.view = "by_name";
vq.nview = strlen(vq.view);
vq.callback = viewCallback;
lcb_error_t rc = lcb_view_query(instance, NULL, &vq);
if (rc != LCB_SUCCESS) {
    // ...
}
lcb_wait(instance);
Note: You can use the lcb_CMDVIEWQUERY's cmdflags to specify additional options, such as LCB_CMDVIEWQUERY_F_INCLUDE_DOCS.