Skip to content

Commit

Permalink
subdoc examples (C, Python)
Browse files Browse the repository at this point in the history
  • Loading branch information
mnunberg committed May 23, 2016
1 parent 02b5c19 commit 61e52cd
Show file tree
Hide file tree
Showing 7 changed files with 392 additions and 1 deletion.
30 changes: 30 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ to Couchbase Server 4.5, and so on.
* [Expiry](#expiry)
* [CAS Handling - Using CAS for concurrent mutations](#cas-handling---using-cas-for-concurrent-mutations)
* [Durability](#durability)
- [Sub-Document Operations](#sub-document-operations)
* [Subdoc - Retrieving](#subdoc---retrieving)
* [Subdoc - Updating/Storing](#subdoc---updatingstoring)
- [N1QL Queries](#n1ql-queries)
* [Query with criteria](#query-with-criteria)
* [Query with placeholders](#query-with-placeholders)
Expand Down Expand Up @@ -170,6 +173,33 @@ Go |
node.js |
PHP

## Sub-Document Operations

Sub-Document operations (new in Couchbase 4.5) allows efficient addressing of sections within documents
(sub-documents). Sub-Document is often abbreviated as _subdoc_.

### Subdoc - Retrieving
Retrieve a few fields from a document; also demonstrate error handling if some fields are missing

[C](c/subdoc-retrieving.cc) |
[Python](python/subdoc-retrieving.py) |
Java |
.NET |
Go |
node.js |
PHP

### Subdoc - Updating/Storing
Modify a few fields within a document. Show error handling and the behavior of the `create` option

[C](c/subdoc-updating.cc) |
[Python](python/subdoc-updating.py) |
Java |
.NET |
Go |
node.js |
PHP

## N1QL Queries

These examples show how to query using N1QL
Expand Down
2 changes: 2 additions & 0 deletions c/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,5 @@ bulk-get
bulk-store
connecting-ssl
*.dSYM
subdoc-updating
subdoc-retrieving
2 changes: 1 addition & 1 deletion c/Makefile
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
PROGS=connecting updating retrieving query-create-index query-criteria \
query-placeholders counter expiration \
query-consistency cas durability bulk-get bulk-store \
connecting-ssl
connecting-ssl subdoc-retrieving subdoc-updating

all: $(PROGS)

Expand Down
111 changes: 111 additions & 0 deletions c/subdoc-retrieving.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
#include <libcouchbase/couchbase.h>
#if LCB_VERSION < 0x020508
#error "Example requires libcouchbase 2.5.8 or greater!"
#endif
#include <libcouchbase/subdoc.h>
#include <string>
#include <vector>

struct Result {
std::string value;
lcb_error_t status;

Result() : status(LCB_SUCCESS) {
}
};

struct SubdocResults {
lcb_error_t status;
std::vector<Result> results;
};

extern "C" {
static void
sdget_callback(lcb_t, int, const lcb_RESPSUBDOC *resp)
{
// "cast" to specific callback type
SubdocResults *results = reinterpret_cast<SubdocResults*>(resp->cookie);
results->status = resp->rc;

if (resp->rc != LCB_SUCCESS && resp->rc != LCB_SUBDOC_MULTI_FAILURE) {
// If the error code is neither SUCCESS nor SUBDOC_MULTI_FAILURE then
// it means that there are no results and an error occurred during
// document access.
return;
}

lcb_SDENTRY ent = { 0 };
size_t ii = 0;
while (lcb_sdresult_next(resp, &ent, &ii)) {
Result r;
r.status = ent.status;
if (ent.nvalue) {
r.value.assign(reinterpret_cast<const char*>(ent.value), ent.nvalue);
}
results->results.push_back(r);
}
}
}

int
main(int, char **)
{
lcb_create_st crst;
lcb_t instance;
memset(&crst, 0, sizeof crst);

crst.version = 3;
crst.v.v3.connstr = "couchbase://localhost/default";

lcb_create(&instance, &crst);
lcb_connect(instance);
lcb_wait(instance);

// Store a key first, so we know it will exist later on. In real production
// environments, we'd also want to install a callback for storage operations
// so we know if they succeeded
lcb_CMDSTORE scmd = { 0 };
const char *key = "a_key";
const char *value = "{\"name\":\"mark\", \"array\":[1,2,3], \"email\":\"m@n.com\"}";
LCB_CMD_SET_KEY(&scmd, key, strlen(key));
LCB_CMD_SET_VALUE(&scmd, value, strlen(value));
scmd.operation = LCB_SET; // Upsert

lcb_store3(instance, NULL, &scmd);
lcb_wait(instance);

// Install the callback for GET operations. Note this can be done at any
// time before the operation is scheduled
lcb_install_callback3(instance, LCB_CALLBACK_SDLOOKUP,
reinterpret_cast<lcb_RESPCALLBACK>(sdget_callback));

SubdocResults my_results;
lcb_SDSPEC specs[3] = { { 0 } };
lcb_CMDSUBDOC sdcmd = { 0 };

LCB_CMD_SET_KEY(&sdcmd, key, strlen(key));

specs[0].sdcmd = LCB_SDCMD_GET;
LCB_SDSPEC_SET_PATH(&specs[0], "email", 5);

specs[1].sdcmd = LCB_SDCMD_GET;
LCB_SDSPEC_SET_PATH(&specs[1], "array[1]", strlen("array[1]"));

specs[2].sdcmd = LCB_SDCMD_EXISTS;
LCB_SDSPEC_SET_PATH(&specs[2], "non-exist", strlen("non-exist"));
sdcmd.specs = specs;
sdcmd.nspecs = 3;

lcb_subdoc3(instance, &my_results, &sdcmd);
lcb_wait(instance);

// Should have three results
assert(my_results.status == LCB_SUBDOC_MULTI_FAILURE);
for (size_t ii = 0; ii < my_results.results.size(); ++ii) {
const Result& r = my_results.results[ii];
printf("Path [%lu]: Status=0x%x (%s). Value=%s\n",
ii, r.status, lcb_strerror(NULL, r.status), r.value.c_str());
}

lcb_destroy(instance);
}
172 changes: 172 additions & 0 deletions c/subdoc-updating.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
// This file is a slight variation from subdoc-retrieving.cc

#include <libcouchbase/couchbase.h>
#if LCB_VERSION < 0x020508
#error "Example requires libcouchbase 2.5.8 or greater!"
#endif
#include <libcouchbase/subdoc.h>
#include <string>
#include <vector>

struct Result {
std::string value;
lcb_error_t status;

Result() : status(LCB_MAX_ERROR) {
}
bool valid() const {
return status != LCB_MAX_ERROR;
}
void reset() {
status = LCB_MAX_ERROR;
value.clear();
}
};

struct SubdocResults {
lcb_error_t status;
std::vector<Result> results;
void reset(size_t max_results) {
results.resize(max_results);
for (size_t ii = 0; ii < max_results; ii++) {
results[ii].reset();
}
}
};

extern "C" {
static void
sdmutate_callback(lcb_t, int, const lcb_RESPSUBDOC *resp)
{
// "cast" to specific callback type
SubdocResults *results = reinterpret_cast<SubdocResults*>(resp->cookie);
results->status = resp->rc;

if (resp->rc != LCB_SUCCESS && resp->rc != LCB_SUBDOC_MULTI_FAILURE) {
// If the error code is neither SUCCESS nor SUBDOC_MULTI_FAILURE then
// it means that there are no results and an error occurred during
// document access.
return;
}

lcb_SDENTRY ent = { 0 };
size_t ii = 0;
while (lcb_sdresult_next(resp, &ent, &ii)) {
// Not all results are returned. Those that are returned
// can be correlated with the request path index by using
// lcb_SDENTRY::index

Result& r = results->results[ent.index];
r.status = ent.status;
if (ent.nvalue) {
r.value.assign(reinterpret_cast<const char*>(ent.value), ent.nvalue);
}
}
}

static void
fulldoc_get_callback(lcb_t, int, const lcb_RESPGET *resp)
{
assert(resp->rc == LCB_SUCCESS);
printf("Document is now: %.*s\n", (int)resp->nvalue, resp->value);
}
}

int
main(int, char **)
{
lcb_create_st crst;
lcb_t instance;
memset(&crst, 0, sizeof crst);

crst.version = 3;
crst.v.v3.connstr = "couchbase://localhost/default";

lcb_create(&instance, &crst);
lcb_connect(instance);
lcb_wait(instance);

// Store a key first, so we know it will exist later on. In real production
// environments, we'd also want to install a callback for storage operations
// so we know if they succeeded
lcb_CMDSTORE scmd = { 0 };
const char *key = "a_key";
const char *value = "{\"name\":\"mark\", \"array\":[1,2,3,4], \"email\":\"m@n.com\"}";
LCB_CMD_SET_KEY(&scmd, key, strlen(key));
LCB_CMD_SET_VALUE(&scmd, value, strlen(value));
scmd.operation = LCB_SET; // Upsert

lcb_store3(instance, NULL, &scmd);
lcb_wait(instance);

// Install the callback for GET operations. Note this can be done at any
// time before the operation is scheduled
lcb_install_callback3(instance, LCB_CALLBACK_SDMUTATE,
reinterpret_cast<lcb_RESPCALLBACK>(sdmutate_callback));

SubdocResults my_results;
lcb_SDSPEC specs[3] = { { 0 } };
lcb_CMDSUBDOC sdcmd = { 0 };

LCB_CMD_SET_KEY(&sdcmd, key, strlen(key));

specs[0].sdcmd = LCB_SDCMD_ARRAY_ADD_LAST;
LCB_SDSPEC_SET_PATH(&specs[0], "array", strlen("array"));
LCB_SDSPEC_SET_VALUE(&specs[0], "42", 2);

specs[1].sdcmd = LCB_SDCMD_COUNTER;
LCB_SDSPEC_SET_PATH(&specs[1], "array[0]", strlen("array[0]"));
LCB_SDSPEC_SET_VALUE(&specs[1], "99", 2);

specs[2].sdcmd = LCB_SDCMD_DICT_UPSERT;
LCB_SDSPEC_SET_PATH(&specs[2], "description", strlen("description"));
LCB_SDSPEC_SET_VALUE(&specs[2], "\"just a dev\"", strlen("\"just a dev\""));
sdcmd.specs = specs;
sdcmd.nspecs = 3;
my_results.reset(3);

lcb_subdoc3(instance, &my_results, &sdcmd);
lcb_wait(instance);

// Should have three results
assert(my_results.status == LCB_SUCCESS);
for (size_t ii = 0; ii < my_results.results.size(); ++ii) {
const Result& r = my_results.results[ii];
if (!r.valid()) {
printf("[%lu]: No output\n", ii);
} else {
printf("[%lu]: %s\n", ii, r.value.c_str());
}
}

lcb_install_callback3(instance, LCB_CALLBACK_GET,
(lcb_RESPCALLBACK)fulldoc_get_callback);

printf("=== Current doc ===\n");
lcb_CMDGET gcmd = { 0 };
LCB_CMD_SET_KEY(&gcmd, key, strlen(key));
lcb_get3(instance, NULL, &gcmd);
lcb_wait(instance);

// Show how to set command options!
memset(&specs[0], 0, sizeof specs[0]);
const char *deep_path = "some.deep.path";
LCB_SDSPEC_SET_PATH(&specs[0], deep_path, strlen(deep_path));
LCB_SDSPEC_SET_VALUE(&specs[0], "true", 4);
specs[0].sdcmd = LCB_SDCMD_DICT_UPSERT;
sdcmd.nspecs = 1;
my_results.reset(1);
lcb_subdoc3(instance, &my_results, &sdcmd);
lcb_wait(instance);

// Should fail with LCB_SUBDOC_PATH_ENOENT
printf("upserting deep path fails: %s\n", lcb_strerror(NULL, my_results.results[0].status));

// Use a flag
specs[0].options = LCB_SDSPEC_F_MKINTERMEDIATES;
lcb_subdoc3(instance, &my_results, &sdcmd);
lcb_wait(instance);
printf("Status with MKINTERMEDIATES: %s\n", lcb_strerror(NULL, my_results.status));

lcb_destroy(instance);
}
42 changes: 42 additions & 0 deletions python/subdoc-retrieving.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
#!/usr/bin/env python
from __future__ import print_function

from couchbase.bucket import Bucket
import couchbase.exceptions as E
import couchbase.subdocument as SD

cb = Bucket('couchbase://localhost/default')

cb.upsert('docid', {
'name': 'Mark',
'email': 'm@n.com',
'array': [1, 2, 3, 4]
})

# Do it the simple way:
rv = cb.retrieve_in('docid', 'name', 'array[1]')
print('Name is: {0}, array[1] is: {1}'.format(rv[0], rv[1]))

# If all results are successful:
name, array_2ndelem = rv
print('Name is: {0}, Array[1] is: {1}'.format(name, array_2ndelem))

# Perform mixed-mode operations
rv = cb.lookup_in('docid',
SD.get('name'), SD.get('array[1]'), SD.exists('non-exist'))
print('Name is', rv[0])
print('Array[1] is', rv[1])
print('non-exist exists?', rv.exists(2))

# See what happens when we try to reference a failed path:
try:
rv[2]
except E.SubdocPathNotFoundError:
print('Using subscript access raises exception for missing item')


# If we try to get a non-existent document, it will fail as normal
try:
cb.retrieve_in('non-exist', 'pth1', 'pth2', 'pth3')
except E.NotFoundError:
print('Document itself not found!')
Loading

0 comments on commit 61e52cd

Please sign in to comment.