couchbase 1.1.5 → 1.2.0.beta

Sign up to get free protection for your applications and to get access to all the features.
Files changed (52) hide show
  1. data/.gitignore +2 -1
  2. data/.travis.yml +12 -1
  3. data/HISTORY.markdown +112 -1
  4. data/README.markdown +149 -6
  5. data/couchbase.gemspec +5 -1
  6. data/ext/couchbase_ext/.gitignore +4 -0
  7. data/ext/couchbase_ext/arguments.c +973 -0
  8. data/ext/couchbase_ext/arithmetic.c +322 -0
  9. data/ext/couchbase_ext/bucket.c +1092 -0
  10. data/ext/couchbase_ext/couchbase_ext.c +618 -3247
  11. data/ext/couchbase_ext/couchbase_ext.h +519 -0
  12. data/ext/couchbase_ext/delete.c +167 -0
  13. data/ext/couchbase_ext/extconf.rb +24 -5
  14. data/ext/couchbase_ext/get.c +301 -0
  15. data/ext/couchbase_ext/gethrtime.c +124 -0
  16. data/ext/couchbase_ext/http.c +402 -0
  17. data/ext/couchbase_ext/observe.c +174 -0
  18. data/ext/couchbase_ext/result.c +126 -0
  19. data/ext/couchbase_ext/stats.c +169 -0
  20. data/ext/couchbase_ext/store.c +522 -0
  21. data/ext/couchbase_ext/timer.c +192 -0
  22. data/ext/couchbase_ext/touch.c +190 -0
  23. data/ext/couchbase_ext/unlock.c +180 -0
  24. data/ext/couchbase_ext/utils.c +471 -0
  25. data/ext/couchbase_ext/version.c +147 -0
  26. data/lib/action_dispatch/middleware/session/couchbase_store.rb +38 -0
  27. data/lib/active_support/cache/couchbase_store.rb +356 -0
  28. data/lib/couchbase.rb +24 -3
  29. data/lib/couchbase/bucket.rb +372 -9
  30. data/lib/couchbase/result.rb +26 -0
  31. data/lib/couchbase/utils.rb +59 -0
  32. data/lib/couchbase/version.rb +1 -1
  33. data/lib/couchbase/view.rb +305 -0
  34. data/lib/couchbase/view_row.rb +230 -0
  35. data/lib/ext/multi_json_fix.rb +47 -0
  36. data/lib/rack/session/couchbase.rb +104 -0
  37. data/tasks/compile.rake +5 -14
  38. data/test/setup.rb +6 -2
  39. data/test/test_arithmetic.rb +32 -2
  40. data/test/test_async.rb +18 -4
  41. data/test/test_bucket.rb +11 -1
  42. data/test/test_cas.rb +13 -3
  43. data/test/test_couchbase_rails_cache_store.rb +294 -0
  44. data/test/test_delete.rb +60 -3
  45. data/test/test_format.rb +28 -17
  46. data/test/test_get.rb +91 -14
  47. data/test/test_store.rb +31 -1
  48. data/test/{test_flush.rb → test_timer.rb} +11 -18
  49. data/test/test_touch.rb +33 -5
  50. data/test/test_unlock.rb +120 -0
  51. data/test/test_utils.rb +26 -0
  52. metadata +101 -8
@@ -0,0 +1,124 @@
1
+ /* -*- Mode: C; tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2
+ /*
3
+ * Copyright 2010, 2011 Couchbase, Inc.
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+
18
+ #include "couchbase_ext.h"
19
+ #include <stdlib.h>
20
+ #include <time.h>
21
+ #include <assert.h>
22
+
23
+ #ifdef HAVE_MACH_MACH_TIME_H
24
+ #include <mach/mach_time.h>
25
+ #endif
26
+
27
+ #ifdef HAVE_SYS_TIME_H
28
+ #include <sys/time.h>
29
+ #endif
30
+
31
+ /*
32
+ * OS X doesn't have clock_gettime, but for monotonic, we can build
33
+ * one with mach_absolute_time as shown below.
34
+ *
35
+ * Most of the idea came from
36
+ * http://www.wand.net.nz/~smr26/wordpress/2009/01/19/monotonic-time-in-mac-os-x/
37
+ */
38
+
39
+ #if defined(HAVE_MACH_ABSOLUTE_TIME) && !defined(HAVE_CLOCK_GETTIME)
40
+
41
+ #define CLOCK_MONOTONIC 192996728
42
+
43
+ static void mach_absolute_difference(uint64_t start, uint64_t end,
44
+ struct timespec *tp)
45
+ {
46
+ uint64_t difference = end - start;
47
+ static mach_timebase_info_data_t info = {0, 0};
48
+
49
+ if (info.denom == 0) {
50
+ mach_timebase_info(&info);
51
+ }
52
+
53
+ uint64_t elapsednano = difference * (info.numer / info.denom);
54
+
55
+ tp->tv_sec = elapsednano * 1e-9;
56
+ tp->tv_nsec = elapsednano - (tp->tv_sec * 1e9);
57
+ }
58
+
59
+ static int clock_gettime(int which, struct timespec *tp)
60
+ {
61
+ assert(which == CLOCK_MONOTONIC);
62
+
63
+ static uint64_t epoch = 0;
64
+
65
+ if (epoch == 0) {
66
+ epoch = mach_absolute_time();
67
+ }
68
+
69
+ uint64_t now = mach_absolute_time();
70
+
71
+ mach_absolute_difference(epoch, now, tp);
72
+
73
+ return 0;
74
+ }
75
+
76
+ #define HAVE_CLOCK_GETTIME 1
77
+ #endif
78
+
79
+ hrtime_t gethrtime(void)
80
+ {
81
+ #ifdef HAVE_CLOCK_GETTIME
82
+ struct timespec tm;
83
+ if (clock_gettime(CLOCK_MONOTONIC, &tm) == -1) {
84
+ abort();
85
+ }
86
+ return (((hrtime_t)tm.tv_sec) * 1000000000) + (hrtime_t)tm.tv_nsec;
87
+ #elif HAVE_GETTIMEOFDAY
88
+
89
+ hrtime_t ret;
90
+ struct timeval tv;
91
+ if (gettimeofday(&tv, NULL) == -1) {
92
+ return (-1ULL);
93
+ }
94
+
95
+ ret = (hrtime_t)tv.tv_sec * 1000000000;
96
+ ret += tv.tv_usec * 1000;
97
+ return ret;
98
+ #elif defined(HAVE_QUERYPERFORMANCECOUNTER)
99
+ double ret;
100
+ // To fix the potential race condition for the local static variable,
101
+ // gethrtime should be called in a global static variable first.
102
+ // It will guarantee the local static variable will be initialized
103
+ // before any thread calls the function.
104
+ static LARGE_INTEGER pf = { 0 };
105
+ static double freq;
106
+ LARGE_INTEGER currtime;
107
+
108
+ if (pf.QuadPart == 0) {
109
+ if (QueryPerformanceFrequency(&pf)) {
110
+ assert(pf.QuadPart != 0);
111
+ freq = 1.0e9 / (double)pf.QuadPart;
112
+ } else {
113
+ abort();
114
+ }
115
+ }
116
+
117
+ QueryPerformanceCounter(&currtime);
118
+
119
+ ret = (double)currtime.QuadPart * freq ;
120
+ return (hrtime_t)ret;
121
+ #else
122
+ #error "I don't know how to build a highres clock..."
123
+ #endif
124
+ }
@@ -0,0 +1,402 @@
1
+ /* vim: ft=c et ts=8 sts=4 sw=4 cino=
2
+ *
3
+ * Copyright 2011, 2012 Couchbase, Inc.
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+
18
+ #include "couchbase_ext.h"
19
+
20
+ void
21
+ http_complete_callback(lcb_http_request_t request, lcb_t handle, const void *cookie, lcb_error_t error, const lcb_http_resp_t *resp)
22
+ {
23
+ struct context_st *ctx = (struct context_st *)cookie;
24
+ struct bucket_st *bucket = ctx->bucket;
25
+ VALUE *rv = ctx->rv, key, val, res;
26
+
27
+ ctx->request->completed = 1;
28
+ key = STR_NEW((const char*)resp->v.v0.path, resp->v.v0.npath);
29
+ ctx->exception = cb_check_error_with_status(error,
30
+ "failed to execute HTTP request", key, resp->v.v0.status);
31
+ if (ctx->exception != Qnil) {
32
+ cb_gc_protect(bucket, ctx->exception);
33
+ }
34
+ val = resp->v.v0.nbytes ? STR_NEW((const char*)resp->v.v0.bytes, resp->v.v0.nbytes) : Qnil;
35
+ if (resp->v.v0.headers) {
36
+ cb_build_headers(ctx, resp->v.v0.headers);
37
+ cb_gc_unprotect(bucket, ctx->headers_val);
38
+ }
39
+ if (ctx->extended) {
40
+ res = rb_class_new_instance(0, NULL, cResult);
41
+ rb_ivar_set(res, id_iv_error, ctx->exception);
42
+ rb_ivar_set(res, id_iv_operation, sym_http_request);
43
+ rb_ivar_set(res, id_iv_key, key);
44
+ rb_ivar_set(res, id_iv_value, val);
45
+ rb_ivar_set(res, id_iv_completed, Qtrue);
46
+ rb_ivar_set(res, id_iv_headers, ctx->headers_val);
47
+ } else {
48
+ res = val;
49
+ }
50
+ if (ctx->proc != Qnil) {
51
+ cb_proc_call(ctx->proc, 1, res);
52
+ }
53
+ if (!bucket->async && ctx->exception == Qnil) {
54
+ *rv = res;
55
+ }
56
+ (void)handle;
57
+ (void)request;
58
+ }
59
+
60
+ void
61
+ http_data_callback(lcb_http_request_t request, lcb_t handle, const void *cookie, lcb_error_t error, const lcb_http_resp_t *resp)
62
+ {
63
+ struct context_st *ctx = (struct context_st *)cookie;
64
+ struct bucket_st *bucket = ctx->bucket;
65
+ VALUE key, val, res;
66
+
67
+ key = STR_NEW((const char*)resp->v.v0.path, resp->v.v0.npath);
68
+ ctx->exception = cb_check_error_with_status(error,
69
+ "failed to execute HTTP request", key, resp->v.v0.status);
70
+ val = resp->v.v0.nbytes ? STR_NEW((const char*)resp->v.v0.bytes, resp->v.v0.nbytes) : Qnil;
71
+ if (ctx->exception != Qnil) {
72
+ cb_gc_protect(bucket, ctx->exception);
73
+ lcb_cancel_http_request(bucket->handle, request);
74
+ }
75
+ if (resp->v.v0.headers) {
76
+ cb_build_headers(ctx, resp->v.v0.headers);
77
+ }
78
+ if (ctx->proc != Qnil) {
79
+ if (ctx->extended) {
80
+ res = rb_class_new_instance(0, NULL, cResult);
81
+ rb_ivar_set(res, id_iv_error, ctx->exception);
82
+ rb_ivar_set(res, id_iv_operation, sym_http_request);
83
+ rb_ivar_set(res, id_iv_key, key);
84
+ rb_ivar_set(res, id_iv_value, val);
85
+ rb_ivar_set(res, id_iv_completed, Qfalse);
86
+ rb_ivar_set(res, id_iv_headers, ctx->headers_val);
87
+ } else {
88
+ res = val;
89
+ }
90
+ cb_proc_call(ctx->proc, 1, res);
91
+ }
92
+ (void)handle;
93
+ }
94
+
95
+ void
96
+ cb_http_request_free(void *ptr)
97
+ {
98
+ struct http_request_st *request = ptr;
99
+ if (request) {
100
+ request->running = 0;
101
+ if (TYPE(request->bucket_obj) == T_DATA
102
+ && RDATA(request->bucket_obj)->dfree == (RUBY_DATA_FUNC)cb_bucket_free
103
+ && !request->completed) {
104
+ lcb_cancel_http_request(request->bucket->handle, request->request);
105
+ }
106
+ xfree((char *)request->cmd.v.v0.content_type);
107
+ xfree((char *)request->cmd.v.v0.path);
108
+ xfree((char *)request->cmd.v.v0.body);
109
+ xfree(request);
110
+ }
111
+ }
112
+
113
+ void
114
+ cb_http_request_mark(void *ptr)
115
+ {
116
+ struct http_request_st *request = ptr;
117
+ if (request) {
118
+ rb_gc_mark(request->on_body_callback);
119
+ }
120
+ }
121
+
122
+ VALUE
123
+ cb_http_request_alloc(VALUE klass)
124
+ {
125
+ VALUE obj;
126
+ struct http_request_st *request;
127
+
128
+ /* allocate new bucket struct and set it to zero */
129
+ obj = Data_Make_Struct(klass, struct http_request_st, cb_http_request_mark,
130
+ cb_http_request_free, request);
131
+ return obj;
132
+ }
133
+
134
+ /*
135
+ * Returns a string containing a human-readable representation of the
136
+ * CouchRequest.
137
+ *
138
+ * @since 1.2.0
139
+ *
140
+ * @return [String]
141
+ */
142
+ VALUE
143
+ cb_http_request_inspect(VALUE self)
144
+ {
145
+ VALUE str;
146
+ struct http_request_st *req = DATA_PTR(self);
147
+ char buf[200];
148
+
149
+ str = rb_str_buf_new2("#<");
150
+ rb_str_buf_cat2(str, rb_obj_classname(self));
151
+ snprintf(buf, 20, ":%p \"", (void *)self);
152
+ rb_str_buf_cat2(str, buf);
153
+ rb_str_buf_cat2(str, req->cmd.v.v0.path);
154
+ snprintf(buf, 100, "\" chunked:%s>", req->cmd.v.v0.chunked ? "true" : "false");
155
+ rb_str_buf_cat2(str, buf);
156
+
157
+ return str;
158
+ }
159
+
160
+ /*
161
+ * Initialize new CouchRequest
162
+ *
163
+ * @since 1.2.0
164
+ *
165
+ * @return [Bucket::CouchRequest]
166
+ */
167
+ VALUE
168
+ cb_http_request_init(int argc, VALUE *argv, VALUE self)
169
+ {
170
+ struct http_request_st *request = DATA_PTR(self);
171
+ VALUE bucket, path, opts, on_body, pp, arg;
172
+ rb_scan_args(argc, argv, "22", &bucket, &pp, &opts, &on_body);
173
+
174
+ if (NIL_P(on_body) && rb_block_given_p()) {
175
+ on_body = rb_block_proc();
176
+ }
177
+ if (CLASS_OF(bucket) != cBucket) {
178
+ rb_raise(rb_eTypeError, "wrong argument type (expected Couchbase::Bucket)");
179
+ }
180
+ memset(&request->cmd, 0, sizeof(lcb_http_cmd_t));
181
+ request->type = LCB_HTTP_TYPE_VIEW;
182
+ request->on_body_callback = on_body;
183
+ request->bucket = DATA_PTR(bucket);
184
+ request->bucket_obj = bucket;
185
+ request->extended = Qfalse;
186
+ path = StringValue(pp); /* convert path to string */
187
+ request->cmd.v.v0.path = strdup(RSTRING_PTR(path));
188
+ request->cmd.v.v0.npath = RSTRING_LEN(path);
189
+ request->cmd.v.v0.method = LCB_HTTP_METHOD_GET;
190
+ request->cmd.v.v0.content_type = strdup("application/json");
191
+
192
+ if (opts != Qnil) {
193
+ Check_Type(opts, T_HASH);
194
+ request->extended = RTEST(rb_hash_aref(opts, sym_extended));
195
+ request->cmd.v.v0.chunked = RTEST(rb_hash_aref(opts, sym_chunked));
196
+ if ((arg = rb_hash_aref(opts, sym_type)) != Qnil) {
197
+ if (arg == sym_view) {
198
+ request->type = LCB_HTTP_TYPE_VIEW;
199
+ } else if (arg == sym_management) {
200
+ request->type = LCB_HTTP_TYPE_MANAGEMENT;
201
+ } else {
202
+ rb_raise(rb_eArgError, "unsupported request type");
203
+ }
204
+ }
205
+ if ((arg = rb_hash_aref(opts, sym_method)) != Qnil) {
206
+ if (arg == sym_get) {
207
+ request->cmd.v.v0.method = LCB_HTTP_METHOD_GET;
208
+ } else if (arg == sym_post) {
209
+ request->cmd.v.v0.method = LCB_HTTP_METHOD_POST;
210
+ } else if (arg == sym_put) {
211
+ request->cmd.v.v0.method = LCB_HTTP_METHOD_PUT;
212
+ } else if (arg == sym_delete) {
213
+ request->cmd.v.v0.method = LCB_HTTP_METHOD_DELETE;
214
+ } else {
215
+ rb_raise(rb_eArgError, "unsupported HTTP method");
216
+ }
217
+ }
218
+ if ((arg = rb_hash_aref(opts, sym_body)) != Qnil) {
219
+ Check_Type(arg, T_STRING);
220
+ request->cmd.v.v0.body = strdup(RSTRING_PTR(arg));
221
+ request->cmd.v.v0.nbody = RSTRING_LEN(arg);
222
+ }
223
+ if ((arg = rb_hash_aref(opts, sym_content_type)) != Qnil) {
224
+ Check_Type(arg, T_STRING);
225
+ xfree((char *)request->cmd.v.v0.content_type);
226
+ request->cmd.v.v0.content_type = strdup(RSTRING_PTR(arg));
227
+ }
228
+ }
229
+
230
+ return self;
231
+ }
232
+
233
+ /*
234
+ * Set +on_body+ callback
235
+ *
236
+ * @since 1.2.0
237
+ */
238
+ VALUE
239
+ cb_http_request_on_body(VALUE self)
240
+ {
241
+ struct http_request_st *request = DATA_PTR(self);
242
+ VALUE old = request->on_body_callback;
243
+ if (rb_block_given_p()) {
244
+ request->on_body_callback = rb_block_proc();
245
+ }
246
+ return old;
247
+ }
248
+
249
+ /*
250
+ * Execute {Bucket::CouchRequest}
251
+ *
252
+ * @since 1.2.0
253
+ */
254
+ VALUE
255
+ cb_http_request_perform(VALUE self)
256
+ {
257
+ struct http_request_st *req = DATA_PTR(self);
258
+ struct context_st *ctx;
259
+ VALUE rv, exc;
260
+ lcb_error_t err;
261
+ struct bucket_st *bucket;
262
+
263
+ ctx = xcalloc(1, sizeof(struct context_st));
264
+ if (ctx == NULL) {
265
+ rb_raise(eClientNoMemoryError, "failed to allocate memory");
266
+ }
267
+ rv = Qnil;
268
+ ctx->rv = &rv;
269
+ ctx->bucket = bucket = req->bucket;
270
+ ctx->proc = rb_block_given_p() ? rb_block_proc() : req->on_body_callback;
271
+ ctx->extended = req->extended;
272
+ ctx->request = req;
273
+ ctx->headers_val = cb_gc_protect(bucket, rb_hash_new());
274
+
275
+ err = lcb_make_http_request(bucket->handle, (const void *)ctx,
276
+ req->type, &req->cmd, &req->request);
277
+ exc = cb_check_error(err, "failed to schedule document request",
278
+ STR_NEW(req->cmd.v.v0.path, req->cmd.v.v0.npath));
279
+ if (exc != Qnil) {
280
+ xfree(ctx);
281
+ rb_exc_raise(exc);
282
+ }
283
+ req->running = 1;
284
+ req->ctx = ctx;
285
+ if (bucket->async) {
286
+ return Qnil;
287
+ } else {
288
+ lcb_wait(bucket->handle);
289
+ if (req->completed) {
290
+ exc = ctx->exception;
291
+ xfree(ctx);
292
+ if (exc != Qnil) {
293
+ cb_gc_unprotect(bucket, exc);
294
+ rb_exc_raise(exc);
295
+ }
296
+ return rv;
297
+ } else {
298
+ return Qnil;
299
+ }
300
+ }
301
+ return Qnil;
302
+ }
303
+
304
+ VALUE
305
+ cb_http_request_pause(VALUE self)
306
+ {
307
+ struct http_request_st *req = DATA_PTR(self);
308
+ req->bucket->io->stop_event_loop(req->bucket->io);
309
+ return Qnil;
310
+ }
311
+
312
+ VALUE
313
+ cb_http_request_continue(VALUE self)
314
+ {
315
+ VALUE exc, *rv;
316
+ struct http_request_st *req = DATA_PTR(self);
317
+
318
+ if (req->running) {
319
+ lcb_wait(req->bucket->handle);
320
+ if (req->completed) {
321
+ exc = req->ctx->exception;
322
+ rv = req->ctx->rv;
323
+ xfree(req->ctx);
324
+ if (exc != Qnil) {
325
+ cb_gc_unprotect(req->bucket, exc);
326
+ rb_exc_raise(exc);
327
+ }
328
+ return *rv;
329
+ }
330
+ } else {
331
+ cb_http_request_perform(self);
332
+ }
333
+ return Qnil;
334
+ }
335
+
336
+ /* Document-method: path
337
+ *
338
+ * @since 1.2.0
339
+ *
340
+ * @return [String] the requested path
341
+ */
342
+ VALUE
343
+ cb_http_request_path_get(VALUE self)
344
+ {
345
+ struct http_request_st *req = DATA_PTR(self);
346
+ return STR_NEW_CSTR(req->cmd.v.v0.path);
347
+ }
348
+
349
+ /* Document-method: chunked
350
+ *
351
+ * @since 1.2.0
352
+ *
353
+ * @return [Boolean] +false+ if library should collect whole response before
354
+ * yielding, +true+ if the client is ready to handle response in chunks.
355
+ */
356
+ VALUE
357
+ cb_http_request_chunked_get(VALUE self)
358
+ {
359
+ struct http_request_st *req = DATA_PTR(self);
360
+ return req->cmd.v.v0.chunked ? Qtrue : Qfalse;
361
+ }
362
+
363
+ /* Document-method: extended
364
+ *
365
+ * @since 1.2.0
366
+ *
367
+ * @return [Boolean] if +false+ the callbacks should receive just the data,
368
+ * and {Couchbase::Result} instance otherwise.
369
+ */
370
+ VALUE
371
+ cb_http_request_extended_get(VALUE self)
372
+ {
373
+ struct http_request_st *req = DATA_PTR(self);
374
+ return req->extended ? Qtrue : Qfalse;
375
+ }
376
+
377
+ /* Document-method: make_http_request(path, options = {})
378
+ *
379
+ * @since 1.2.0
380
+ *
381
+ * @param path [String]
382
+ * @param options [Hash]
383
+ * @option options [Boolean] :extended (false) set it to +true+ if the
384
+ * {Couchbase::Result} object needed. The response chunk will be
385
+ * accessible through +#value+ attribute.
386
+ * @yieldparam [String,Couchbase::Result] res the response chunk if the
387
+ * :extended option is +false+ and result object otherwise
388
+ *
389
+ * @return [Couchbase::Bucket::CouchRequest]
390
+ */
391
+ VALUE
392
+ cb_bucket_make_http_request(int argc, VALUE *argv, VALUE self)
393
+ {
394
+ VALUE args[4]; /* bucket, path, options, block */
395
+
396
+ args[0] = self;
397
+ rb_scan_args(argc, argv, "11&", &args[1], &args[2], &args[3]);
398
+
399
+ return rb_class_new_instance(4, args, cCouchRequest);
400
+ }
401
+
402
+