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,192 @@
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
+ cb_timer_free(void *ptr)
22
+ {
23
+ xfree(ptr);
24
+ }
25
+
26
+ void
27
+ cb_timer_mark(void *ptr)
28
+ {
29
+ struct timer_st *timer = ptr;
30
+ if (timer) {
31
+ rb_gc_mark(timer->callback);
32
+ }
33
+ }
34
+
35
+ VALUE
36
+ cb_timer_alloc(VALUE klass)
37
+ {
38
+ VALUE obj;
39
+ struct timer_st *timer;
40
+
41
+ /* allocate new bucket struct and set it to zero */
42
+ obj = Data_Make_Struct(klass, struct timer_st, cb_timer_mark,
43
+ cb_timer_free, timer);
44
+ return obj;
45
+ }
46
+
47
+ /*
48
+ * Returns a string containing a human-readable representation of the
49
+ * Timer.
50
+ *
51
+ * @since 1.2.0.dp6
52
+ *
53
+ * @return [String]
54
+ */
55
+ VALUE
56
+ cb_timer_inspect(VALUE self)
57
+ {
58
+ VALUE str;
59
+ struct timer_st *tm = DATA_PTR(self);
60
+ char buf[200];
61
+
62
+ str = rb_str_buf_new2("#<");
63
+ rb_str_buf_cat2(str, rb_obj_classname(self));
64
+ snprintf(buf, 20, ":%p", (void *)self);
65
+ rb_str_buf_cat2(str, buf);
66
+ snprintf(buf, 100, " timeout:%u periodic:%s>",
67
+ tm->usec, tm->periodic ? "true" : "false");
68
+ rb_str_buf_cat2(str, buf);
69
+
70
+ return str;
71
+ }
72
+
73
+ /*
74
+ * Cancel the timer.
75
+ *
76
+ * @since 1.2.0.dp6
77
+ *
78
+ * This operation makes sense for periodic timers or if one need to cancel
79
+ * regular timer before it will be triggered.
80
+ *
81
+ * @example Cancel periodic timer
82
+ * n = 1
83
+ * c.run do
84
+ * tm = c.create_periodic_timer(500000) do
85
+ * c.incr("foo") do
86
+ * if n == 5
87
+ * tm.cancel
88
+ * else
89
+ * n += 1
90
+ * end
91
+ * end
92
+ * end
93
+ * end
94
+ *
95
+ * @return [String]
96
+ */
97
+ VALUE
98
+ cb_timer_cancel(VALUE self)
99
+ {
100
+ struct timer_st *tm = DATA_PTR(self);
101
+ lcb_timer_destroy(tm->bucket->handle, tm->timer);
102
+ return self;
103
+ }
104
+
105
+ static VALUE
106
+ trigger_timer(VALUE timer)
107
+ {
108
+ struct timer_st *tm = DATA_PTR(timer);
109
+ return cb_proc_call(tm->callback, 1, timer);
110
+ }
111
+
112
+ static void
113
+ timer_callback(lcb_timer_t timer, lcb_t instance,
114
+ const void *cookie)
115
+ {
116
+ struct timer_st *tm = (struct timer_st *)cookie;
117
+ int error = 0;
118
+
119
+ rb_protect(trigger_timer, tm->self, &error);
120
+ if (error) {
121
+ lcb_timer_destroy(instance, timer);
122
+ }
123
+ (void)cookie;
124
+ }
125
+
126
+ /*
127
+ * Initialize new Timer
128
+ *
129
+ * @since 1.2.0
130
+ *
131
+ * The timers could used to trigger reccuring events or implement timeouts.
132
+ * The library will call given block after time interval pass.
133
+ *
134
+ * @param bucket [Bucket] the connection object
135
+ * @param interval [Fixnum] the interval in microseconds
136
+ * @param options [Hash]
137
+ * @option options [Boolean] :periodic (false) set it to +true+ if the timer
138
+ * should be triggered until it will be canceled.
139
+ *
140
+ * @yieldparam [Timer] timer the current timer
141
+ *
142
+ * @example Create regular timer for 0.5 second
143
+ * c.run do
144
+ * Couchbase::Timer.new(c, 500000) do
145
+ * puts "ding-dong"
146
+ * end
147
+ * end
148
+ *
149
+ * @example Create periodic timer
150
+ * n = 10
151
+ * c.run do
152
+ * Couchbase::Timer.new(c, 500000, :periodic => true) do |tm|
153
+ * puts "#{n}"
154
+ * n -= 1
155
+ * tm.cancel if n.zero?
156
+ * end
157
+ * end
158
+ *
159
+ *
160
+ * @return [Couchbase::Timer]
161
+ */
162
+ VALUE
163
+ cb_timer_init(int argc, VALUE *argv, VALUE self)
164
+ {
165
+ struct timer_st *tm = DATA_PTR(self);
166
+ VALUE bucket, opts, timeout, exc, cb;
167
+ lcb_error_t err;
168
+
169
+ rb_need_block();
170
+ rb_scan_args(argc, argv, "21&", &bucket, &timeout, &opts, &cb);
171
+
172
+ if (CLASS_OF(bucket) != cBucket) {
173
+ rb_raise(rb_eTypeError, "wrong argument type (expected Couchbase::Bucket)");
174
+ }
175
+ tm->self = self;
176
+ tm->callback = cb;
177
+ tm->usec = NUM2ULONG(timeout);
178
+ tm->bucket = DATA_PTR(bucket);
179
+ if (opts != Qnil) {
180
+ Check_Type(opts, T_HASH);
181
+ tm->periodic = RTEST(rb_hash_aref(opts, sym_periodic));
182
+ }
183
+ tm->timer = lcb_timer_create(tm->bucket->handle, tm, tm->usec,
184
+ tm->periodic, timer_callback, &err);
185
+ exc = cb_check_error(err, "failed to attach the timer", Qnil);
186
+ if (exc != Qnil) {
187
+ rb_exc_raise(exc);
188
+ }
189
+
190
+ return self;
191
+ }
192
+
@@ -0,0 +1,190 @@
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
+ touch_callback(lcb_t handle, const void *cookie, lcb_error_t error, const lcb_touch_resp_t *resp)
22
+ {
23
+ struct context_st *ctx = (struct context_st *)cookie;
24
+ struct bucket_st *bucket = ctx->bucket;
25
+ VALUE key, *rv = ctx->rv, exc = Qnil, res;
26
+
27
+ ctx->nqueries--;
28
+ key = STR_NEW((const char*)resp->v.v0.key, resp->v.v0.nkey);
29
+ strip_key_prefix(bucket, key);
30
+
31
+ if (error != LCB_KEY_ENOENT || !ctx->quiet) {
32
+ exc = cb_check_error(error, "failed to touch value", key);
33
+ if (exc != Qnil) {
34
+ rb_ivar_set(exc, id_iv_operation, sym_touch);
35
+ if (NIL_P(ctx->exception)) {
36
+ ctx->exception = cb_gc_protect(bucket, exc);
37
+ }
38
+ }
39
+ }
40
+
41
+ if (bucket->async) { /* asynchronous */
42
+ if (ctx->proc != Qnil) {
43
+ res = rb_class_new_instance(0, NULL, cResult);
44
+ rb_ivar_set(res, id_iv_error, exc);
45
+ rb_ivar_set(res, id_iv_operation, sym_touch);
46
+ rb_ivar_set(res, id_iv_key, key);
47
+ cb_proc_call(ctx->proc, 1, res);
48
+ }
49
+ } else { /* synchronous */
50
+ rb_hash_aset(*rv, key, (error == LCB_SUCCESS) ? Qtrue : Qfalse);
51
+ }
52
+ if (ctx->nqueries == 0) {
53
+ cb_gc_unprotect(bucket, ctx->proc);
54
+ }
55
+ (void)handle;
56
+ }
57
+
58
+ /*
59
+ * Update the expiry time of an item
60
+ *
61
+ * @since 1.0.0
62
+ *
63
+ * The +touch+ method allow you to update the expiration time on a given
64
+ * key. This can be useful for situations where you want to prevent an item
65
+ * from expiring without resetting the associated value. For example, for a
66
+ * session database you might want to keep the session alive in the database
67
+ * each time the user accesses a web page without explicitly updating the
68
+ * session value, keeping the user's session active and available.
69
+ *
70
+ * @overload touch(key, options = {})
71
+ * @param key [String, Symbol] Key used to reference the value.
72
+ * @param options [Hash] Options for operation.
73
+ * @option options [Fixnum] :ttl (self.default_ttl) Expiry time for key.
74
+ * Values larger than 30*24*60*60 seconds (30 days) are interpreted as
75
+ * absolute times (from the epoch).
76
+ * @option options [true, false] :quiet (self.quiet) If set to +true+, the
77
+ * operation won't raise error for missing key, it will return +nil+.
78
+ *
79
+ * @yieldparam ret [Result] the result of operation in asynchronous mode
80
+ * (valid attributes: +error+, +operation+, +key+).
81
+ *
82
+ * @return [true, false] +true+ if the operation was successful and +false+
83
+ * otherwise.
84
+ *
85
+ * @raise [Couchbase::Error::Connect] if connection closed (see {Bucket#reconnect})
86
+ *
87
+ * @raise [ArgumentError] when passing the block in synchronous mode
88
+ *
89
+ * @example Touch value using +default_ttl+
90
+ * c.touch("foo")
91
+ *
92
+ * @example Touch value using custom TTL (10 seconds)
93
+ * c.touch("foo", :ttl => 10)
94
+ *
95
+ * @overload touch(keys)
96
+ * @param keys [Hash] The Hash where keys represent the keys in the
97
+ * database, values -- the expiry times for corresponding key. See
98
+ * description of +:ttl+ argument above for more information about TTL
99
+ * values.
100
+ *
101
+ * @yieldparam ret [Result] the result of operation for each key in
102
+ * asynchronous mode (valid attributes: +error+, +operation+, +key+).
103
+ *
104
+ * @return [Hash] Mapping keys to result of touch operation (+true+ if the
105
+ * operation was successful and +false+ otherwise)
106
+ *
107
+ * @example Touch several values
108
+ * c.touch("foo" => 10, :bar => 20) #=> {"foo" => true, "bar" => true}
109
+ *
110
+ * @example Touch several values in async mode
111
+ * c.run do
112
+ * c.touch("foo" => 10, :bar => 20) do |ret|
113
+ * ret.operation #=> :touch
114
+ * ret.success? #=> true
115
+ * ret.key #=> "foo" and "bar" in separate calls
116
+ * end
117
+ * end
118
+ *
119
+ * @example Touch single value
120
+ * c.touch("foo" => 10) #=> true
121
+ *
122
+ */
123
+ VALUE
124
+ cb_bucket_touch(int argc, VALUE *argv, VALUE self)
125
+ {
126
+ struct bucket_st *bucket = DATA_PTR(self);
127
+ struct context_st *ctx;
128
+ VALUE args, rv, proc, exc;
129
+ lcb_error_t err;
130
+ struct params_st params;
131
+
132
+ if (bucket->handle == NULL) {
133
+ rb_raise(eConnectError, "closed connection");
134
+ }
135
+ rb_scan_args(argc, argv, "0*&", &args, &proc);
136
+ if (!bucket->async && proc != Qnil) {
137
+ rb_raise(rb_eArgError, "synchronous mode doesn't support callbacks");
138
+ }
139
+ rb_funcall(args, id_flatten_bang, 0);
140
+ memset(&params, 0, sizeof(struct params_st));
141
+ params.type = cmd_touch;
142
+ params.bucket = bucket;
143
+ cb_params_build(&params, RARRAY_LEN(args), args);
144
+ ctx = xcalloc(1, sizeof(struct context_st));
145
+ if (ctx == NULL) {
146
+ rb_raise(eClientNoMemoryError, "failed to allocate memory for context");
147
+ }
148
+ ctx->proc = cb_gc_protect(bucket, proc);
149
+ ctx->bucket = bucket;
150
+ rv = rb_hash_new();
151
+ ctx->rv = &rv;
152
+ ctx->exception = Qnil;
153
+ ctx->quiet = params.cmd.touch.quiet;
154
+ ctx->nqueries = params.cmd.touch.num;
155
+ err = lcb_touch(bucket->handle, (const void *)ctx,
156
+ params.cmd.touch.num, params.cmd.touch.ptr);
157
+ cb_params_destroy(&params);
158
+ exc = cb_check_error(err, "failed to schedule touch request", Qnil);
159
+ if (exc != Qnil) {
160
+ xfree(ctx);
161
+ rb_exc_raise(exc);
162
+ }
163
+ bucket->nbytes += params.npayload;
164
+ if (bucket->async) {
165
+ maybe_do_loop(bucket);
166
+ return Qnil;
167
+ } else {
168
+ if (ctx->nqueries > 0) {
169
+ /* we have some operations pending */
170
+ lcb_wait(bucket->handle);
171
+ }
172
+ exc = ctx->exception;
173
+ xfree(ctx);
174
+ if (exc != Qnil) {
175
+ rb_exc_raise(cb_gc_unprotect(bucket, exc));
176
+ }
177
+ if (bucket->exception != Qnil) {
178
+ rb_exc_raise(bucket->exception);
179
+ }
180
+ if (params.cmd.touch.num > 1) {
181
+ return rv; /* return as a hash {key => true, ...} */
182
+ } else {
183
+ VALUE vv = Qnil;
184
+ rb_hash_foreach(rv, cb_first_value_i, (VALUE)&vv);
185
+ return vv;
186
+ }
187
+ }
188
+ }
189
+
190
+
@@ -0,0 +1,180 @@
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
+ unlock_callback(lcb_t handle, const void *cookie, lcb_error_t error, const lcb_unlock_resp_t *resp)
22
+ {
23
+ struct context_st *ctx = (struct context_st *)cookie;
24
+ struct bucket_st *bucket = ctx->bucket;
25
+ VALUE key, *rv = ctx->rv, exc = Qnil, res;
26
+
27
+ ctx->nqueries--;
28
+ key = STR_NEW((const char*)resp->v.v0.key, resp->v.v0.nkey);
29
+ strip_key_prefix(bucket, key);
30
+
31
+ if (error != LCB_KEY_ENOENT || !ctx->quiet) {
32
+ exc = cb_check_error(error, "failed to unlock value", key);
33
+ if (exc != Qnil) {
34
+ rb_ivar_set(exc, id_iv_operation, sym_unlock);
35
+ if (NIL_P(ctx->exception)) {
36
+ ctx->exception = cb_gc_protect(bucket, exc);
37
+ }
38
+ }
39
+ }
40
+
41
+ if (bucket->async) { /* asynchronous */
42
+ if (ctx->proc != Qnil) {
43
+ res = rb_class_new_instance(0, NULL, cResult);
44
+ rb_ivar_set(res, id_iv_error, exc);
45
+ rb_ivar_set(res, id_iv_operation, sym_unlock);
46
+ rb_ivar_set(res, id_iv_key, key);
47
+ cb_proc_call(ctx->proc, 1, res);
48
+ }
49
+ } else { /* synchronous */
50
+ rb_hash_aset(*rv, key, (error == LCB_SUCCESS) ? Qtrue : Qfalse);
51
+ }
52
+ if (ctx->nqueries == 0) {
53
+ cb_gc_unprotect(bucket, ctx->proc);
54
+ }
55
+ (void)handle;
56
+ }
57
+
58
+ /*
59
+ * Unlock key
60
+ *
61
+ * @since 1.2.0
62
+ *
63
+ * The +unlock+ method allow you to unlock key once locked by {Bucket#get}
64
+ * with +:lock+ option.
65
+ *
66
+ * @overload unlock(key, options = {})
67
+ * @param key [String, Symbol] Key used to reference the value.
68
+ * @param options [Hash] Options for operation.
69
+ * @option options [Fixnum] :cas The CAS value must match the current one
70
+ * from the storage.
71
+ * @option options [true, false] :quiet (self.quiet) If set to +true+, the
72
+ * operation won't raise error for missing key, it will return +nil+.
73
+ *
74
+ * @return [true, false] +true+ if the operation was successful and +false+
75
+ * otherwise.
76
+ *
77
+ * @raise [Couchbase::Error::Connect] if connection closed (see {Bucket#reconnect})
78
+ *
79
+ * @raise [ArgumentError] when passing the block in synchronous mode
80
+ *
81
+ * @raise [Couchbase::Error::NotFound] if key(s) not found in the storage
82
+ *
83
+ * @raise [Couchbase::Error::TemporaryFail] if either the key wasn't
84
+ * locked or given CAS value doesn't match to actual in the storage
85
+ *
86
+ * @example Unlock the single key
87
+ * val, _, cas = c.get("foo", :lock => true, :extended => true)
88
+ * c.unlock("foo", :cas => cas)
89
+ *
90
+ * @overload unlock(keys)
91
+ * @param keys [Hash] The Hash where keys represent the keys in the
92
+ * database, values -- the CAS for corresponding key.
93
+ *
94
+ * @yieldparam ret [Result] the result of operation for each key in
95
+ * asynchronous mode (valid attributes: +error+, +operation+, +key+).
96
+ *
97
+ * @return [Hash] Mapping keys to result of unlock operation (+true+ if the
98
+ * operation was successful and +false+ otherwise)
99
+ *
100
+ * @example Unlock several keys
101
+ * c.unlock("foo" => cas1, :bar => cas2) #=> {"foo" => true, "bar" => true}
102
+ *
103
+ * @example Unlock several values in async mode
104
+ * c.run do
105
+ * c.unlock("foo" => 10, :bar => 20) do |ret|
106
+ * ret.operation #=> :unlock
107
+ * ret.success? #=> true
108
+ * ret.key #=> "foo" and "bar" in separate calls
109
+ * end
110
+ * end
111
+ *
112
+ */
113
+ VALUE
114
+ cb_bucket_unlock(int argc, VALUE *argv, VALUE self)
115
+ {
116
+ struct bucket_st *bucket = DATA_PTR(self);
117
+ struct context_st *ctx;
118
+ VALUE args, rv, proc, exc;
119
+ lcb_error_t err;
120
+ struct params_st params;
121
+
122
+ if (bucket->handle == NULL) {
123
+ rb_raise(eConnectError, "closed connection");
124
+ }
125
+ rb_scan_args(argc, argv, "0*&", &args, &proc);
126
+ if (!bucket->async && proc != Qnil) {
127
+ rb_raise(rb_eArgError, "synchronous mode doesn't support callbacks");
128
+ }
129
+ rb_funcall(args, id_flatten_bang, 0);
130
+ memset(&params, 0, sizeof(struct params_st));
131
+ params.type = cmd_unlock;
132
+ params.bucket = bucket;
133
+ cb_params_build(&params, RARRAY_LEN(args), args);
134
+ ctx = xcalloc(1, sizeof(struct context_st));
135
+ if (ctx == NULL) {
136
+ rb_raise(eClientNoMemoryError, "failed to allocate memory for context");
137
+ }
138
+ ctx->proc = cb_gc_protect(bucket, proc);
139
+ ctx->bucket = bucket;
140
+ rv = rb_hash_new();
141
+ ctx->rv = &rv;
142
+ ctx->exception = Qnil;
143
+ ctx->quiet = params.cmd.unlock.quiet;
144
+ ctx->nqueries = params.cmd.unlock.num;
145
+ err = lcb_unlock(bucket->handle, (const void *)ctx,
146
+ params.cmd.unlock.num, params.cmd.unlock.ptr);
147
+ cb_params_destroy(&params);
148
+ exc = cb_check_error(err, "failed to schedule unlock request", Qnil);
149
+ if (exc != Qnil) {
150
+ xfree(ctx);
151
+ rb_exc_raise(exc);
152
+ }
153
+ bucket->nbytes += params.npayload;
154
+ if (bucket->async) {
155
+ maybe_do_loop(bucket);
156
+ return Qnil;
157
+ } else {
158
+ if (ctx->nqueries > 0) {
159
+ /* we have some operations pending */
160
+ lcb_wait(bucket->handle);
161
+ }
162
+ exc = ctx->exception;
163
+ xfree(ctx);
164
+ if (exc != Qnil) {
165
+ rb_exc_raise(cb_gc_unprotect(bucket, exc));
166
+ }
167
+ if (bucket->exception != Qnil) {
168
+ rb_exc_raise(bucket->exception);
169
+ }
170
+ if (params.cmd.unlock.num > 1) {
171
+ return rv; /* return as a hash {key => true, ...} */
172
+ } else {
173
+ VALUE vv = Qnil;
174
+ rb_hash_foreach(rv, cb_first_value_i, (VALUE)&vv);
175
+ return vv;
176
+ }
177
+ }
178
+ }
179
+
180
+