N1QL Queries using the C (libcouchbase) SDK with Couchbase Server

This page describes how to issue N1QL queries using the C SDK. N1QL queries are performed using a row-based API which invokes a callback repeatedly, once for each query row. The C SDK uses a special JSON parser to invoke row callbacks as they are read from the network - avoiding waiting for the entire response to be received.

N1QL queries are performed using a row-based API. This API is similar in spirit to the view API (see MapReduce Views using the C (libcouchbase) SDK with Couchbase Server). The N1QL API is available when the libcouchbase/n1ql.h file is included.

To execute a N1QL query, first declare your handler. The handler is called once for each JSON-encoded row, and then one last time with the LCB_RESP_F_FINAL bit set in the rflags response member, where any result metadata (including errors) is returned. To actually make sense of the row’s content, use a JSON decoder on the row and nrow buffer/length fields.

N1QL Row Handler
static void rowCallback(lcb_t instance, int cbtype, const lcb_RESPN1QL *resp) {
    if (! (resp->rflags & LCB_RESP_F_FINAL)) {
        printf("Row: %.*s\n", (int)resp->nrow, resp->row);
    } else {
        printf("Got metadata: %.*s\n", (int)resp->nrow, resp->row);
    }
}

To issue the actual query, populate an lcb_CMDN1QL structure with appropriate parameters. Some of the internals of this structure may be populated using the lcb_N1QLPARAMS object. The lcb_N1QLPARAMS object is provided as a higher-level means by which to construct N1QL queries and supports N1QL features such as query placeholders and prepared statements. A named or positional parameter is a placeholder for a value in the WHERE, LIMIT or OFFSET clause of a query. For example, to issue a query with a placeholder:

  1. Create the params object (lcb_n1p_new()).

  2. Set the query string (lcb_n1p_setstmtz(params, "statement_string")).

  3. Set the placeholders (lcb_n1p_namedparamz(params, "$param1", "value1"); lcb_n1p_namedparamz(params, "$param2", "value2")).

  4. Populate the lcb_CMDN1QL structure with the encoded query (lcb_n1p_mkcmd(params, &cmd)).

  5. Issue the query (lcb_n1ql_query).

  6. (Optional): You can dump the encoded form of the query using the return value from lcb_n1p_encode(). This returns a null-terminated string.

  7. Free or clear the params object (lcb_n1p_free or lcb_n1p_reset).

Here’s a code example that uses placeholders

Issuing a N1QL query with a placeholder
lcb_N1QLPARAMS *params = lcb_n1p_new();
lcb_CMDN1QL cmd = { 0 };

// Need to make this properly formatted JSON
std::string city_str;
city_str += '"';
city_str += city;
city_str += '"';

rc = lcb_n1p_setstmtz(params,
    "SELECT airportname FROM `travel-sample` "
    "WHERE city=$1 AND type=\"airport\"");
rc = lcb_n1p_posparam(params, city_str.c_str(), city_str.size());

cmd.callback = query_callback;

rc = lcb_n1p_mkcmd(params, &cmd);
rc = lcb_n1ql_query(instance, cookie, &cmd);
lcb_n1p_free(params);
lcb_wait(instance);

You can also utilize the encoded query directly (without using lcb_N1QLPARAMS). This involves using a pre-encoded query per the N1QL REST API. This example issues the same query as above, bypassing the parameters object, and encoding the query manually.

User-encoded query
const char *querystr =
    "{"
        /* read as SELECT fname || " " || lname FROM default WHERE age > $age LIMIT 5 */
        "\"statement\":"SELECT fname || \" \" || lname, age FROM default WHERE age > $age LIMIT 5\","
        "\"$age\": 27"
    "}"
lcb_CMDN1QL cmd = { 0 };
cmd.query = querystr;
cmd.nquery = strlen(querystr);
cmd.callback = rowCallback;
lcb_error_t rc = lcb_n1ql_query(instance, NULL, &cmd);
// ...
Versions prior to 2.5.3 require the content_type field to be set to application/json. Since version 2.5.3, all queries must be in JSON, and the content_type field is ignored.

Scan Consistency

Setting a staleness parameter for queries, with scan_consistency, enables a tradeoff between latency and (eventual) consistency. A N1QL query using the default Not Bounded Scan Consistency will not wait for any indexes to finish updating before running the query and returning results, meaning that results are returned quickly, but the query will not return any documents yet to be indexed.

With Scan Consistency set to RequestPlus, all document changes and index updates are processed before the query is run. Select this when consistency is always more important than performance. For a middle ground, AtPlus is a "read your own write" (RYOW) option, which means it just waits for the new documents that you specify to be indexed, rather than an entire index of multiple documents. See the examples for how to use AtPlus for the best performance balance for many circumstances.

Prepared Statements

Since version 2.5.3, applications may optimize frequently issued statements by having the client internally prepare them. To use prepared statements, simply set the LCB_CMDN1QL_F_PREPCACHE bit in the cmdflags field

lcb_CMDN1QL cmd = { 0 };
// initialize other sections
cmd.cmdflags |= LCB_CMDN1QL_F_PREPCACHE;