Created
November 30, 2012 17:02
-
-
Save mnunberg/4177010 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #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; | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /** | |
| * 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_ */ |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #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); | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #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