Skip to content

Instantly share code, notes, and snippets.

@mnunberg
Created November 30, 2012 17:02
Show Gist options
  • Select an option

  • Save mnunberg/4177010 to your computer and use it in GitHub Desktop.

Select an option

Save mnunberg/4177010 to your computer and use it in GitHub Desktop.
#include <libcouchbase/couchbase.h>
#include "viewrow.h"
#include "viewopts.h"
#include <stdio.h>
#undef NDEBUG
#include <assert.h>
#include <sys/stat.h>
#define OV1(p) ((p)->v.v1)
#define OV0(p) ((p)->v.v0)
static void
my_vr_callback(lcb_vrow_ctx_t *ctx,
const void *cookie,
const lcb_vrow_datum_t *res)
{
if (res->type == LCB_VROW_ERROR) {
printf("Got parse error..\n");
abort();
}
if (res->type == LCB_VROW_COMPLETE) {
printf("Rows done. Metadata is here..\n");
printf("%.*s\n",
(int)res->ndata,
res->data);
return;
}
if (*res->data != '{' || res->data[res->ndata-1] != '}') {
abort();
}
printf("== Row Callback == (n=%lu)\n", res->ndata);
}
static void
http_data_callback(lcb_http_request_t request,
lcb_t instance,
const void *cookie,
lcb_error_t error,
const lcb_http_resp_t *resp)
{
lcb_vrow_ctx_t *rctx = (lcb_vrow_ctx_t*)cookie;
printf("Got callback..\n");
printf("Got status %d\n", resp->v.v0.status);
assert(error == LCB_SUCCESS);
if (resp->v.v0.nbytes) {
lcb_vrow_feed(rctx,
(const char*)resp->v.v0.bytes,
resp->v.v0.nbytes);
} else {
printf("No data.. hrm.\n");
}
}
char *
get_view_buffer(const char *fname)
{
FILE *fp = fopen(fname, "r");
assert(fp);
struct stat sb;
int ret = fstat(fileno(fp), &sb);
assert(ret != -1);
char *buf = malloc(sb.st_size + 1);
buf[sb.st_size] = '\0';
fread(buf, 1, sb.st_size, fp);
fclose(fp);
return buf;
}
enum {
OPTIX_STALE = 0,
OPTIX_LIMIT,
OPTIX_GROUP,
OPTIX_DEBUG,
_OPTIX_MAX
};
static void
schedule_http(lcb_t instance)
{
lcb_http_cmd_t htcmd;
lcb_http_request_t htreq;
lcb_error_t err;
char *vqstr;
char *errstr;
size_t num_options;
lcb_vopt_t *vopt_list, *vopt_ptarray[10];
lcb_vrow_ctx_t *rctx = lcb_vrow_create();
int ii;
err = lcb_vopt_createv(&vopt_list,
&num_options,
&errstr,
"stale", "false",
"limit", "300",
"debug", "true",
NULL);
assert(err == LCB_SUCCESS);
assert(num_options);
for (ii = 0; ii < num_options; ii++) {
vopt_ptarray[ii] = vopt_list + ii;
}
vqstr = lcb_vqstr_make_uri("beer", -1,
"brewery_beers", -1,
(const lcb_vopt_t* const*)vopt_ptarray,
num_options);
memset(&htcmd, 0, sizeof(htcmd));
OV0(&htcmd).content_type = "application/json";
OV0(&htcmd).path = vqstr;
OV0(&htcmd).npath = strlen(vqstr);
OV0(&htcmd).chunked = 1;
OV0(&htcmd).method = LCB_HTTP_METHOD_GET;
rctx->callback = my_vr_callback;
lcb_set_http_data_callback(instance, http_data_callback);
err = lcb_make_http_request(instance,
rctx,
LCB_HTTP_TYPE_VIEW,
&htcmd,
&htreq);
assert(err == LCB_SUCCESS);
printf("Waiting for request..\n");
err = lcb_wait(instance);
assert (err == LCB_SUCCESS);
// printf("Got query string: %s\n", vqstr);
free(vqstr);
lcb_vopt_cleanup_list(&vopt_list, num_options, 1);
free(vopt_list);
lcb_vrow_free(rctx);
}
static lcb_t
create_handle(void)
{
struct lcb_create_st ctor_opts;
lcb_t instance = NULL;
lcb_error_t err;
memset(&ctor_opts, 0, sizeof(ctor_opts));
OV1(&ctor_opts).bucket = "beer-sample";
OV1(&ctor_opts).user = "Administrator";
OV1(&ctor_opts).passwd = "123456";
OV1(&ctor_opts).host = "127.0.0.1:8091";
if (LCB_SUCCESS != lcb_create(&instance, &ctor_opts)) {
abort();
}
err = lcb_connect(instance);
assert(err == LCB_SUCCESS);
err = lcb_wait(instance);
assert(err == LCB_SUCCESS);
return instance;
}
static void
test_vopts(void)
{
/**
* Create some view options..
*/
lcb_vopt_t opt_stale;
lcb_vopt_t opt_onerr;
lcb_vopt_t opt_limit;
lcb_vopt_t opt_invalid;
lcb_vopt_t *optlist[] = {
&opt_stale,
&opt_onerr,
&opt_limit,
&opt_invalid
};
const lcb_vopt_t * const * vl_const = (const lcb_vopt_t* const* )optlist;
lcb_error_t err;
char *estr = NULL;
int optval;
int opttype;
/**
* Use constants (and avoid mistyping strings) for options which support it
*/
opttype = LCB_VOPT_OPT_STALE;
optval = 0;
err = lcb_vopt_assign(&opt_stale,
&opttype,
0,
&optval,
0,
LCB_VOPT_F_OPTNAME_NUMERIC
| LCB_VOPT_F_OPTVAL_NUMERIC,
&estr);
assert (err == LCB_SUCCESS);
/**
* Strings!
*/
err = lcb_vopt_assign(&opt_onerr,
"on_error", -1,
"continue", -1,
0,
&estr);
assert (err == LCB_SUCCESS);
optval = 10;
/* mix them around */
err = lcb_vopt_assign(&opt_limit,
"limit", -1,
&optval, 0,
LCB_VOPT_F_OPTVAL_NUMERIC,
&estr);
assert (err == LCB_SUCCESS);
/* Invalid options fail */
err = lcb_vopt_assign(&opt_invalid,
"invalid_option_string", -1,
"invalid value", -1,
0, &estr);
/* Try it again, with a special flag */
assert(err == LCB_EINVAL);
printf("%s\n", estr);
/* Try it again, with a special flag */
err = lcb_vopt_assign(&opt_invalid,
"invalid_option_string",
-1,
"[\"json\",\"encoded\"]",
-1,
LCB_VOPT_F_PASSTHROUGH | LCB_VOPT_F_PCTENCODE,
&estr);
assert(err == LCB_SUCCESS);
int num_options = sizeof(optlist) / sizeof(*optlist);
printf("Estimated length: %d\n",
lcb_vqstr_calc_len(vl_const, num_options));
char buf[128] = { 0 };
lcb_vqstr_write(vl_const, num_options, buf);
printf("Got buffer %s\n", buf);
char *auto_buf = lcb_vqstr_make_uri("design_doc", -1,
"view_doc", -1,
vl_const, num_options);
printf("Got request URI: %s\n", auto_buf);
free(auto_buf);
lcb_vopt_cleanup_list(optlist, num_options, 0);
}
static void
test_refused(lcb_t handle)
{
lcb_http_cmd_t cmd;
lcb_http_request_t req;
memset(&cmd, 0, sizeof(cmd));
cmd.version = 1;
OV1(&cmd).host = "127.0.0.1:2";
OV1(&cmd).path = "/";
OV1(&cmd).npath = 1;
OV1(&cmd).content_type = "application/json";
OV1(&cmd).method = LCB_HTTP_METHOD_GET;
lcb_error_t err;
err = lcb_make_http_request(handle, NULL, LCB_HTTP_TYPE_RAW, &cmd, &req);
assert(err == LCB_SUCCESS);
lcb_wait(handle);
printf("Wait done..\n");
}
static void
test_vrow(void)
{
lcb_vrow_ctx_t *rctx = lcb_vrow_create();
rctx->callback = my_vr_callback;
char *view_out = get_view_buffer("views.out");
size_t stream_len = strlen(view_out);
char *view_last = view_out + stream_len, *view_cur = view_out;
while (view_cur < view_last) {
size_t cur_len = view_last - view_cur;
if (cur_len > 3) {
cur_len = 3;
}
lcb_vrow_feed(rctx, view_cur, cur_len);
view_cur += cur_len;
}
size_t meta_len;
const char *meta_data = lcb_vrow_get_meta(rctx, &meta_len);
printf("Sekeleton: %.*s\n", meta_len, meta_data);
lcb_vrow_free(rctx);
free(view_out);
}
int main(void)
{
lcb_t instance = create_handle();
// test_refused(instance);
schedule_http(instance);
lcb_destroy(instance);
test_vopts();
test_vrow();
return 0;
}
/**
* View extensions for libcouchbase.
*
* This contains functions to:
*
* o Build and validate view options
* o Encode required view option values into JSON
* o Encode required view option values into percent-encoding
*/
#ifndef LCB_VIEWOPTS_H_
#define LCB_VIEWOPTS_H_
#ifdef __cplusplus
extern "C" {
#endif
#include "libcouchbase/couchbase.h"
enum {
/* encode the value in percent-encoding if needed */
LCB_VOPT_F_PCTENCODE = 1<<0,
/* option value should be treated as an int (and possibly coerced)
* rather than a string
*/
LCB_VOPT_F_OPTVAL_NUMERIC = 1<<1,
LCB_VOPT_F_PASSTHROUGH = 1<<2,
/* option value is constant. Don't free */
LCB_VOPT_F_OPTVAL_CONSTANT = 1<<3,
/* option name is constant. Don't free */
LCB_VOPT_F_OPTNAME_CONSTANT = 1<<4,
/* Option name is an integer constant, not a string */
LCB_VOPT_F_OPTNAME_NUMERIC = 1<<5,
};
/**
* This x-macro accepts three arguments:
* (1) The 'base' constant name for the view option
* (2) The string name (as is encoded into the URI)
* (3) The expected type. These are used internally. Types are:
* 'bool' - coerced into a 'true' or 'false' string
* 'num' - coerced into a numeric string
* 'string' - optionally percent-encoded
* 'jval' - aliased to string, but means a JSON-encoded primitive or
* complex value
* 'jarry' - a JSON array
* 'onerror' - special type accepting the appropriate values (stop, continue)
* 'stale' - special type accepting ('ok' (coerced if needed from true), 'false',
* and 'update_after')
*/
#define LCB_XVOPT \
XX(DESCENDING, "descending", bool) \
XX(ENDKEY, "endkey", jval) \
XX(ENDKEY_DOCID, "endkey_docid", string) \
XX(FULLSET, "full_set", bool) \
XX(GROUP, "group", bool) \
XX(GROUP_LEVEL, "group_level", num) \
XX(INCLUSIVE_END, "inclusive_end", bool) \
XX(KEYS, "keys", jarry) \
XX(SINGLE_KEY, "key", jval) \
XX(ONERROR, "on_error", onerror) \
XX(REDUCE, "reduce", bool) \
XX(STALE, "stale", stale) \
XX(SKIP, "skip", num) \
XX(LIMIT, "limit", num) \
XX(STARTKEY, "startkey", jval) \
XX(STARTKEY_DOCID, "startkey_docid", string) \
XX(DEBUG, "debug", bool)
enum {
LCB_VOPT_OPT_CLIENT_PASSTHROUGH = 0,
#define XX(b, str, type) \
LCB_VOPT_OPT_##b,
LCB_XVOPT
#undef XX
_LCB_VOPT_OPT_MAX
};
typedef struct lcb_vopt_st {
/* NUL-terminated option name */
const char *optname;
/* NUL-terminated option value */
const char *optval;
size_t noptname;
size_t noptval;
int flags;
} lcb_vopt_t;
/**
* Properly initializes a view_option structure, checking (if requested)
* for valid option names and inputs
*
* @param optobj an allocated but empty optobj structure
*
* @param option An option name.
* This may be a string or something else dependent on the flags
*
* @param noption Size of an option (if the option name is a string)
*
* @param value The value for the option. This may be a string (defaul) or
* something else depending on the flags. If a string, it must be UTF-8 compatible
*
* @param nvalue the sizeo of the value (if the value is a string).
*
* @param flags. A set of flags to specify for the conversion
*
* @param error_string An error string describing the details of why validation
* failed. This is a constant string and should not be freed. Only valid if the
* function does not return LCB_SUCEESS
*
* @return LCB_SUCCESS if the validation/conversion was a success, or an error
* code (typically LCB_EINVAL) otherwise.
*
* If the operation succeeded, the option object should be cleaned up using
* free_view_option which will clean up necessary fields within the structure.
* As the actual structure is not allocated by the library, the library will
* not free the structure.
*/
lcb_error_t
lcb_vopt_assign(lcb_vopt_t *optobj,
const void *option,
size_t noption,
const void *value,
size_t nvalue,
int flags,
char **error_string);
/**
* Creates an array of options from a list of strings. The list should be
* NULL terminated
* @param optarray a pointer which will contain an array of vopts
* @param noptions will contain the number of vopts
* @param errstr - will contain a pointer to a string upon error
* @param .. "key", "value" pairs
* @return LCB_SUCCESS on success, error otherwise. All memory is freed on error;
* otherwise the list must be freed using vopt_cleanup
*/
lcb_error_t
lcb_vopt_createv(lcb_vopt_t *optarray[],
size_t *noptions, char **errstr, ...);
/**
* Cleans up a vopt structure. This does not free the structure, but does
* free any allocated members in the structure's internal fields (if any(.
*/
void
lcb_vopt_cleanup(lcb_vopt_t *optobj);
/**
* Convenience function to free a list of options. This is here since many
* of the functions already require an lcb_vopt_st **
*
* @param options a list of options
* @param noptions how many options
* @param contiguous whether the pointer points to an array of pointers,
* (i.e. where *(options[n]) dereferneces the structure, or a pointer to a
* continuous block of memory, so that (*options)[n] dereferences
*/
void
lcb_vopt_cleanup_list(lcb_vopt_t ** options, size_t noptions,
int contiguous);
/**
* Calculates the minimum size of the query portion of a buffer
*/
size_t
lcb_vqstr_calc_len(const lcb_vopt_t * const * options,
size_t noptions);
/**
* Writes the query string to a buffer. The buffer must have enough space
* as determined by vqstr_calc_len.
* Returns how many bytes were actually written to the buffer
*/
size_t
lcb_vqstr_write(const lcb_vopt_t * const * options, size_t noptions,
char *buf);
/**
* Creates a proper URI query string for a view with its parameters.
*
* @param design the name of the design document
* @param ndesign length of design name (-1 for nul-terminated)
* @param view the name of the view to query
* @param nview the length of the view name (-1 for nul-terminated)
* @param options the view options for this query
* @param noptions how many options
*
* @return an allocated string (via malloc) which may be used for the view
* query. The string will be NUL-terminated so strlen may be called to obtain
* the length.
*/
char *
lcb_vqstr_make_uri(const char *design, size_t ndesign,
const char *view, size_t nview,
const lcb_vopt_t * const * options,
size_t noptions);
#ifdef __cplusplus
}
#endif /* __cplusplus */
#endif /* LCB_VIEWOPTS_H_ */
#include "viewrow.h"
#include <assert.h>
#define DECLARE_JSONSL_CALLBACK(name) \
static void name(\
jsonsl_t,jsonsl_action_t,\
struct jsonsl_state_st*,const char*)
DECLARE_JSONSL_CALLBACK(row_pop_callback);
DECLARE_JSONSL_CALLBACK(initial_push_callback);
DECLARE_JSONSL_CALLBACK(initial_pop_callback);
DECLARE_JSONSL_CALLBACK(meta_header_complete_callback);
DECLARE_JSONSL_CALLBACK(trailer_pop_callback);
/* conform to void */
#define JOBJ_RESPONSE_ROOT (void*)1
#define JOBJ_ROWSET (void*)2
static void
buffer_append(lcb_vrow_buffer *vb, const void *data, size_t ndata)
{
if (vb->alloc - vb->len < ndata) {
/* multiple of two */
size_t wanted_size = 64;
while (wanted_size < ndata + vb->len) {
wanted_size *= 2;
}
vb->alloc = wanted_size;
vb->s = realloc(vb->s, vb->alloc);
}
memcpy(vb->s + vb->len, data, ndata);
vb->len += ndata;
}
static void
buffer_reset(lcb_vrow_buffer *vb, int free_chunk)
{
vb->len = 0;
if (free_chunk) {
free(vb->s);
vb->alloc = 0;
}
}
/**
* Gets a buffer, given an (absolute) position offset.
* It will try to get a buffer of size desired. The actual size is
* returned in 'actual' (and may be less than desired, maybe even 0)
*/
static const char *
get_buffer_region(lcb_vrow_ctx_t *ctx, size_t pos, size_t desired,
size_t *actual)
{
const char *ret = ctx->current_buf.s + pos - ctx->min_pos;
const char *end = ctx->current_buf.s + ctx->current_buf.len;
*actual = end - ret;
if (ctx->min_pos > pos) {
/* swallowed */
*actual = 0;
return NULL;
}
assert(ret < end);
if (desired < *actual) {
*actual = desired;
}
return ret;
}
/**
* Consolidate the meta data into a single parsable string..
*/
static void
combine_meta(lcb_vrow_ctx_t *ctx)
{
const char *meta_trailer;
size_t ntrailer;
if (ctx->meta_complete) {
return;
}
assert(ctx->header_len <= ctx->meta_buf.len);
/* Adjust the length for the first portion */
ctx->meta_buf.len = ctx->header_len;
/* Append any trailing data */
meta_trailer = get_buffer_region(ctx,
ctx->last_row_endpos + 1, -1, &ntrailer);
buffer_append(&ctx->meta_buf, meta_trailer, ntrailer);
ctx->meta_complete = 1;
}
#define NORMALIZE_OFFSETS(buf, len) \
buf++; /* beginning of '"' */ \
len--;
static void
meta_header_complete_callback(jsonsl_t jsn,
jsonsl_action_t action,
struct jsonsl_state_st *state,
const jsonsl_char_t *at)
{
lcb_vrow_ctx_t *ctx = (lcb_vrow_ctx_t*)jsn->data;
buffer_append(&ctx->meta_buf,
ctx->current_buf.s, state->pos_begin);
ctx->header_len = state->pos_begin;
jsn->action_callback_PUSH = NULL;
}
static void
row_pop_callback(jsonsl_t jsn,
jsonsl_action_t action,
struct jsonsl_state_st *state,
const jsonsl_char_t *at)
{
lcb_vrow_ctx_t *ctx = (lcb_vrow_ctx_t*)jsn->data;
const char *rowbuf;
size_t szdummy;
if (ctx->have_error) {
return;
}
ctx->keep_pos = state->pos_cur;
ctx->last_row_endpos = state->pos_cur;
ctx->rowcount++;
if (state->data == JOBJ_ROWSET) {
/* don't care anymore.. */
jsn->action_callback_POP = trailer_pop_callback;
jsn->action_callback_PUSH = NULL;
return;
}
/* must be a JSON object! */
if (!ctx->callback) {
return;
}
rowbuf = get_buffer_region(ctx, state->pos_begin, -1, &szdummy);
{
/**
* Create our context..
*/
lcb_vrow_datum_t dt = { 0 };
dt.type = LCB_VROW_ROW;
dt.data = rowbuf;
dt.ndata = state->pos_cur - state->pos_begin + 1;
ctx->callback(ctx, ctx->user_cookie, &dt);
}
}
static int
parse_error_callback(jsonsl_t jsn,
jsonsl_error_t error,
struct jsonsl_state_st *state,
jsonsl_char_t *at)
{
lcb_vrow_ctx_t *ctx = (lcb_vrow_ctx_t*)jsn->data;
ctx->have_error = 1;
{
/* invoke the callback */
lcb_vrow_datum_t dt = { 0 };
dt.type = LCB_VROW_ERROR;
dt.data = ctx->current_buf.s;
dt.ndata = ctx->current_buf.len;
ctx->callback(ctx, ctx->user_cookie, &dt);
}
return 0;
}
static void
trailer_pop_callback(jsonsl_t jsn,
jsonsl_action_t action,
struct jsonsl_state_st *state,
const jsonsl_char_t *at)
{
lcb_vrow_ctx_t *ctx = (lcb_vrow_ctx_t*)jsn->data;
lcb_vrow_datum_t dt = { 0 };
if (state->data != JOBJ_RESPONSE_ROOT) {
return;
}
combine_meta(ctx);
dt.data = ctx->meta_buf.s;
dt.ndata = ctx->meta_buf.len;
dt.type = LCB_VROW_COMPLETE;
ctx->callback(ctx, ctx->user_cookie, &dt);
}
/**
*
*/
static void
initial_pop_callback(jsonsl_t jsn,
jsonsl_action_t action,
struct jsonsl_state_st *state,
const jsonsl_char_t *at)
{
lcb_vrow_ctx_t *ctx = (lcb_vrow_ctx_t*)jsn->data;
char *key;
int len;
if (ctx->have_error) {
return;
}
if (JSONSL_STATE_IS_CONTAINER(state)) {
return;
}
if (state->type != JSONSL_T_HKEY) {
return;
}
key = ctx->current_buf.s + state->pos_begin;
len = state->pos_cur - state->pos_begin;
NORMALIZE_OFFSETS(key, len);
buffer_reset(&ctx->last_hk, 0);
buffer_append(&ctx->last_hk, key, len);
}
/**
* This is called for the first few tokens, where we are still searching
* for the row set.
*/
static void
initial_push_callback(jsonsl_t jsn,
jsonsl_action_t action,
struct jsonsl_state_st *state,
const jsonsl_char_t *at)
{
(void)action; /* always PUSH */
lcb_vrow_ctx_t *ctx = (lcb_vrow_ctx_t*)jsn->data;
jsonsl_jpr_match_t match;
if (ctx->have_error) {
printf("Have error..\n");
return;
}
if (JSONSL_STATE_IS_CONTAINER(state)) {
jsonsl_jpr_match_state(jsn,
state,
ctx->last_hk.s,
ctx->last_hk.len,
&match);
}
buffer_reset(&ctx->last_hk, 0);
if (ctx->initialized == 0) {
if (state->type != JSONSL_T_OBJECT) {
printf("Not an object..\n");
ctx->have_error = 1;
return;
}
if (match != JSONSL_MATCH_POSSIBLE) {
ctx->have_error = 1;
return;
}
/* tag the state */
state->data = JOBJ_RESPONSE_ROOT;
ctx->initialized = 1;
return;
}
if (state->type == JSONSL_T_LIST && match == JSONSL_MATCH_POSSIBLE) {
/* we have a match */
jsn->action_callback_POP = row_pop_callback;
jsn->action_callback_PUSH = meta_header_complete_callback;
state->data = JOBJ_ROWSET;
}
}
static void
feed_data(lcb_vrow_ctx_t *ctx, const char *data, size_t ndata)
{
size_t old_len = ctx->current_buf.len;
buffer_append(&ctx->current_buf, data, ndata);
jsonsl_feed(ctx->jsn, ctx->current_buf.s + old_len, ndata);
/**
* Do we need to cut off some bytes?
*/
if (ctx->keep_pos > ctx->min_pos) {
size_t lentmp, diff = ctx->keep_pos - ctx->min_pos;
const char *buf = get_buffer_region(ctx,
ctx->keep_pos, -1, &lentmp);
memmove(ctx->current_buf.s,
buf,
ctx->current_buf.len - diff);
ctx->current_buf.len -= diff;
}
ctx->min_pos = ctx->keep_pos;
}
/* Non-static wrapper */
void
lcb_vrow_feed(lcb_vrow_ctx_t *ctx, const char *data, size_t ndata)
{
feed_data(ctx, data, ndata);
}
const char *
lcb_vrow_get_meta(lcb_vrow_ctx_t *ctx, size_t *len)
{
combine_meta(ctx);
*len = ctx->meta_buf.len;
return ctx->meta_buf.s;
}
lcb_vrow_ctx_t*
lcb_vrow_create(void)
{
lcb_vrow_ctx_t *ctx;
jsonsl_error_t err;
ctx = calloc(1, sizeof(*ctx));
ctx->jsn = jsonsl_new(512);
ctx->jpr = jsonsl_jpr_new("/rows/^", &err);
if (!ctx->jpr) {
abort();
}
jsonsl_jpr_match_state_init(ctx->jsn, &ctx->jpr, 1);
lcb_vrow_reset(ctx);
return ctx;
}
void
lcb_vrow_reset(lcb_vrow_ctx_t* ctx)
{
/**
* We create a copy, and set its relevant fields. All other
* fields are zeroed implicitly. Then we copy the object back.
*/
lcb_vrow_ctx_t ctx_copy = { 0 };
jsonsl_reset(ctx->jsn);
buffer_reset(&ctx->current_buf, 0);
buffer_reset(&ctx->meta_buf, 0);
buffer_reset(&ctx->last_hk, 0);
/**
* Initially all callbacks are enabled so that we can search for the
* rows array.
*/
ctx->jsn->action_callback_POP = initial_pop_callback;
ctx->jsn->action_callback_PUSH = initial_push_callback;
ctx->jsn->error_callback = parse_error_callback;
ctx->jsn->max_callback_level = 4;
ctx->jsn->data = ctx;
jsonsl_enable_all_callbacks(ctx->jsn);
ctx_copy.jsn = ctx->jsn;
ctx_copy.user_cookie = ctx->user_cookie;
ctx_copy.callback = ctx->callback;
ctx_copy.jpr = ctx->jpr;
ctx_copy.current_buf = ctx->current_buf;
ctx_copy.meta_buf = ctx->meta_buf;
ctx_copy.last_hk = ctx->last_hk;
*ctx = ctx_copy;
}
void
lcb_vrow_free(lcb_vrow_ctx_t *ctx)
{
jsonsl_jpr_match_state_cleanup(ctx->jsn);
jsonsl_destroy(ctx->jsn);
jsonsl_jpr_destroy(ctx->jpr);
buffer_reset(&ctx->current_buf, 1);
buffer_reset(&ctx->meta_buf, 1);
buffer_reset(&ctx->last_hk, 1);
free(ctx);
}
#ifndef LCB_VIEWROW_H_
#define LCB_VIEWROW_H_
#ifdef __cplusplus
extern "C" {
#endif
#include "jsonsl.h"
#include <libcouchbase/couchbase.h>
typedef struct lcb_rows_ctx_st lcb_vrow_ctx_t;
typedef enum {
/**
* This is a row of view data. You can parse this as JSON from your
* favorite decoder/converter
*/
LCB_VROW_ROW,
/**
* All the rows have been returned. In this case, the data is the 'meta'.
* This is a valid JSON payload which was returned from the server.
* The "rows" : [] array will be empty.
*/
LCB_VROW_COMPLETE,
/**
* A JSON parse error occured. The payload will contain string data. This
* may be JSON (but this is not likely).
* The callback will be delivered twice. First when the error is noticed,
* and second at the end (instead of a COMPLETE callback)
*/
LCB_VROW_ERROR
} lcb_vrow_type_t;
typedef struct {
/** The type of data encapsulated */
lcb_vrow_type_t type;
/** string data */
const char *data;
/** length */
size_t ndata;
} lcb_vrow_datum_t;
typedef void (*lcb_vrow_callback_t)(lcb_vrow_ctx_t *ctx,
const void *cookie,
const lcb_vrow_datum_t *resp);
/**
* Do we always need to always make these lame structures?
*/
typedef struct {
char *s;
size_t len;
size_t alloc;
} lcb_vrow_buffer;
struct lcb_rows_ctx_st {
/* jsonsl parser */
jsonsl_t jsn;
/* jsonpointer match object */
jsonsl_jpr_t jpr;
/* buffer containing the skeleton */
lcb_vrow_buffer meta_buf;
/* scratch/read buffer */
lcb_vrow_buffer current_buf;
/* last hash key */
lcb_vrow_buffer last_hk;
/* flags. This should be an int with a bunch of constant flags */
int have_error;
int initialized;
int meta_complete;
unsigned rowcount;
/* absolute position offset corresponding to the first byte in current_buf */
size_t min_pos;
/* minimum (absolute) position to keep */
size_t keep_pos;
/**
* size of the metadata header chunk (i.e. everything until the opening
* bracket of "rows" [
*/
size_t header_len;
/**
* Position of last row returned. If there are no subsequent rows, this
* signals the beginning of the metadata trailer
*/
size_t last_row_endpos;
/**
* User stuff:
*/
/* wrapped cookie */
void *user_cookie;
/* callback to invoke */
lcb_vrow_callback_t callback;
};
/**
* Creates a new vrow context object.
* You must set callbacks on this object if you wish it to be useful.
* You must feed it data (calling vrow_feed) as well. The data may be fed
* in chunks and callbacks will be invoked as each row is read.
*/
lcb_vrow_ctx_t*
lcb_vrow_create(void);
#define lcb_vrow_set_callback(vr, cb) vr->callback = cb
#define lcb_vrow_set_cookie(vr, cookie) vr->user_cookie = cookie
/**
* Frees a vrow object created by vrow_create
*/
void
lcb_vrow_free(lcb_vrow_ctx_t *ctx);
/**
* Feeds data into the vrow. The callback may be invoked multiple times
* in this function. In the context of normal lcb usage, this will typically
* be invoked from within an http_data_callback.
*/
void
lcb_vrow_feed(lcb_vrow_ctx_t *ctx, const char *data, size_t ndata);
/**
* Gets the metadata from the vrow
*/
const char *
lcb_vrow_get_meta(lcb_vrow_ctx_t *ctx, size_t *len);
/**
* Gets a chunk of data from the vrow. There is no telling what the format
* of the contained data will be; thus there is no guarantee that it will be
* parseable as complete JSON.
*
* This is mainly useful for debugging non-success view responses
*/
const char *
lcb_vrow_get_raw(lcb_vrow_ctx_t *ctx, size_t *len);
#ifdef __cplusplus
}
#endif
#endif /* LCB_VIEWROW_H_ */
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment