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,167 @@
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
+ delete_callback(lcb_t handle, const void *cookie, lcb_error_t error, const lcb_remove_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 remove value", key);
33
+ if (exc != Qnil) {
34
+ rb_ivar_set(exc, id_iv_operation, sym_delete);
35
+ if (NIL_P(ctx->exception)) {
36
+ ctx->exception = cb_gc_protect(bucket, exc);
37
+ }
38
+ }
39
+ }
40
+ if (bucket->async) { /* asynchronous */
41
+ if (ctx->proc != Qnil) {
42
+ res = rb_class_new_instance(0, NULL, cResult);
43
+ rb_ivar_set(res, id_iv_error, exc);
44
+ rb_ivar_set(res, id_iv_operation, sym_delete);
45
+ rb_ivar_set(res, id_iv_key, key);
46
+ cb_proc_call(ctx->proc, 1, res);
47
+ }
48
+ } else { /* synchronous */
49
+ rb_hash_aset(*rv, key, (error == LCB_SUCCESS) ? Qtrue : Qfalse);
50
+ }
51
+ if (ctx->nqueries == 0) {
52
+ cb_gc_unprotect(bucket, ctx->proc);
53
+ }
54
+ (void)handle;
55
+ }
56
+
57
+ /*
58
+ * Delete the specified key
59
+ *
60
+ * @since 1.0.0
61
+ *
62
+ * @overload delete(key, options = {})
63
+ * @param key [String, Symbol] Key used to reference the value.
64
+ * @param options [Hash] Options for operation.
65
+ * @option options [true, false] :quiet (self.quiet) If set to +true+, the
66
+ * operation won't raise error for missing key, it will return +nil+.
67
+ * Otherwise it will raise error in synchronous mode. In asynchronous
68
+ * mode this option ignored.
69
+ * @option options [Fixnum] :cas The CAS value for an object. This value
70
+ * created on the server and is guaranteed to be unique for each value of
71
+ * a given key. This value is used to provide simple optimistic
72
+ * concurrency control when multiple clients or threads try to
73
+ * update/delete an item simultaneously.
74
+ *
75
+ * @raise [Couchbase::Error::Connect] if connection closed (see {Bucket#reconnect})
76
+ * @raise [ArgumentError] when passing the block in synchronous mode
77
+ * @raise [Couchbase::Error::KeyExists] on CAS mismatch
78
+ * @raise [Couchbase::Error::NotFound] if key is missing in verbose mode
79
+ *
80
+ * @return [true, false, Hash<String, Boolean>] the result of the
81
+ * operation
82
+ *
83
+ * @example Delete the key in quiet mode (default)
84
+ * c.set("foo", "bar")
85
+ * c.delete("foo") #=> true
86
+ * c.delete("foo") #=> false
87
+ *
88
+ * @example Delete the key verbosely
89
+ * c.set("foo", "bar")
90
+ * c.delete("foo", :quiet => false) #=> true
91
+ * c.delete("foo", :quiet => true) #=> nil (default behaviour)
92
+ * c.delete("foo", :quiet => false) #=> will raise Couchbase::Error::NotFound
93
+ *
94
+ * @example Delete the key with version check
95
+ * ver = c.set("foo", "bar") #=> 5992859822302167040
96
+ * c.delete("foo", :cas => 123456) #=> will raise Couchbase::Error::KeyExists
97
+ * c.delete("foo", :cas => ver) #=> true
98
+ */
99
+ VALUE
100
+ cb_bucket_delete(int argc, VALUE *argv, VALUE self)
101
+ {
102
+ struct bucket_st *bucket = DATA_PTR(self);
103
+ struct context_st *ctx;
104
+ VALUE rv, exc;
105
+ VALUE args, proc;
106
+ lcb_error_t err;
107
+ struct params_st params;
108
+
109
+ if (bucket->handle == NULL) {
110
+ rb_raise(eConnectError, "closed connection");
111
+ }
112
+ rb_scan_args(argc, argv, "0*&", &args, &proc);
113
+ if (!bucket->async && proc != Qnil) {
114
+ rb_raise(rb_eArgError, "synchronous mode doesn't support callbacks");
115
+ }
116
+ rb_funcall(args, id_flatten_bang, 0);
117
+ memset(&params, 0, sizeof(struct params_st));
118
+ params.type = cmd_remove;
119
+ params.bucket = bucket;
120
+ cb_params_build(&params, RARRAY_LEN(args), args);
121
+
122
+ ctx = xcalloc(1, sizeof(struct context_st));
123
+ if (ctx == NULL) {
124
+ rb_raise(eClientNoMemoryError, "failed to allocate memory for context");
125
+ }
126
+ ctx->quiet = params.cmd.remove.quiet;
127
+ ctx->proc = cb_gc_protect(bucket, proc);
128
+ rv = rb_hash_new();
129
+ ctx->rv = &rv;
130
+ ctx->bucket = bucket;
131
+ ctx->exception = Qnil;
132
+ ctx->nqueries = params.cmd.remove.num;
133
+ err = lcb_remove(bucket->handle, (const void *)ctx,
134
+ params.cmd.remove.num, params.cmd.remove.ptr);
135
+ cb_params_destroy(&params);
136
+ exc = cb_check_error(err, "failed to schedule delete request", Qnil);
137
+ if (exc != Qnil) {
138
+ xfree(ctx);
139
+ rb_exc_raise(exc);
140
+ }
141
+ bucket->nbytes += params.npayload;
142
+ if (bucket->async) {
143
+ maybe_do_loop(bucket);
144
+ return Qnil;
145
+ } else {
146
+ if (ctx->nqueries > 0) {
147
+ /* we have some operations pending */
148
+ lcb_wait(bucket->handle);
149
+ }
150
+ exc = ctx->exception;
151
+ xfree(ctx);
152
+ if (exc != Qnil) {
153
+ rb_exc_raise(cb_gc_unprotect(bucket, exc));
154
+ }
155
+ if (bucket->exception != Qnil) {
156
+ rb_exc_raise(bucket->exception);
157
+ }
158
+ if (params.cmd.remove.num > 1) {
159
+ return rv; /* return as a hash {key => true, ...} */
160
+ } else {
161
+ VALUE vv = Qnil;
162
+ rb_hash_foreach(rv, cb_first_value_i, (VALUE)&vv);
163
+ return vv;
164
+ }
165
+ return rv;
166
+ }
167
+ }
@@ -16,7 +16,22 @@
16
16
  # limitations under the License.
17
17
  #
18
18
 
19
- ENV['RC_ARCHS'] = '' if RUBY_PLATFORM =~ /darwin/
19
+ require 'rbconfig'
20
+ # This hack is more robust, because in bundler environment bundler touches
21
+ # all constants from rbconfig.rb before loading any scripts. This is why
22
+ # RC_ARCHS doesn't work under bundler on MacOS.
23
+ if RUBY_PLATFORM =~ /darwin/
24
+ [RbConfig::CONFIG, RbConfig::MAKEFILE_CONFIG].each do |cfg|
25
+ cfg["CFLAGS"].gsub!(RbConfig::ARCHFLAGS, '')
26
+ cfg["LDFLAGS"].gsub!(RbConfig::ARCHFLAGS, '')
27
+ cfg["LDSHARED"].gsub!(RbConfig::ARCHFLAGS, '')
28
+ cfg["LIBRUBY_LDSHARED"].gsub!(RbConfig::ARCHFLAGS, '')
29
+ cfg["configure_args"].gsub!(RbConfig::ARCHFLAGS, '')
30
+ end
31
+ end
32
+
33
+ # Unset RUBYOPT to avoid interferences
34
+ ENV['RUBYOPT'] = nil
20
35
 
21
36
  require 'mkmf'
22
37
 
@@ -105,9 +120,13 @@ if try_compile(<<-SRC)
105
120
  end
106
121
 
107
122
 
108
- if RbConfig::CONFIG['target_os'] =~ /mingw32/
109
- have_library("vbucket", "vbucket_config_create", "libvbucket/vbucket.h") or abort "You should install libvbucket >= 1.8.0.2"
110
- end
111
- have_library("couchbase", "libcouchbase_server_versions", "libcouchbase/couchbase.h") or abort "You should install libcouchbase >= 1.0.2"
123
+ have_library("couchbase", "lcb_get", "libcouchbase/couchbase.h") or abort "You should install libcouchbase >= 2.0.0. See http://www.couchbase.com/develop/ for more details"
124
+ have_header("mach/mach_time.h")
125
+ have_header("stdint.h") or abort "Failed to locate stdint.h"
126
+ have_header("sys/time.h")
127
+ have_func("clock_gettime")
128
+ have_func("gettimeofday")
129
+ have_func("QueryPerformanceCounter")
130
+ define("_GNU_SOURCE")
112
131
  create_header("couchbase_config.h")
113
132
  create_makefile("couchbase_ext")
@@ -0,0 +1,301 @@
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
+ get_callback(lcb_t handle, const void *cookie, lcb_error_t error, const lcb_get_resp_t *resp)
22
+ {
23
+ struct context_st *ctx = (struct context_st *)cookie;
24
+ struct bucket_st *bucket = ctx->bucket;
25
+ VALUE key, val, flags, cas, *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 get value", key);
33
+ if (exc != Qnil) {
34
+ rb_ivar_set(exc, id_iv_operation, sym_get);
35
+ if (NIL_P(ctx->exception)) {
36
+ ctx->exception = cb_gc_protect(bucket, exc);
37
+ }
38
+ }
39
+ }
40
+
41
+ flags = ULONG2NUM(resp->v.v0.flags);
42
+ cas = ULL2NUM(resp->v.v0.cas);
43
+ val = Qnil;
44
+ if (resp->v.v0.nbytes != 0) {
45
+ val = decode_value(STR_NEW((const char*)resp->v.v0.bytes, resp->v.v0.nbytes),
46
+ resp->v.v0.flags, ctx->force_format);
47
+ if (val == Qundef) {
48
+ if (ctx->exception != Qnil) {
49
+ cb_gc_unprotect(bucket, ctx->exception);
50
+ }
51
+ ctx->exception = rb_exc_new2(eValueFormatError, "unable to convert value");
52
+ rb_ivar_set(ctx->exception, id_iv_operation, sym_get);
53
+ rb_ivar_set(ctx->exception, id_iv_key, key);
54
+ cb_gc_protect(bucket, ctx->exception);
55
+ }
56
+ } else if (flags_get_format(resp->v.v0.flags) == sym_plain) {
57
+ val = STR_NEW_CSTR("");
58
+ }
59
+ if (bucket->async) { /* asynchronous */
60
+ if (ctx->proc != Qnil) {
61
+ res = rb_class_new_instance(0, NULL, cResult);
62
+ rb_ivar_set(res, id_iv_error, exc);
63
+ rb_ivar_set(res, id_iv_operation, sym_get);
64
+ rb_ivar_set(res, id_iv_key, key);
65
+ rb_ivar_set(res, id_iv_value, val);
66
+ rb_ivar_set(res, id_iv_flags, flags);
67
+ rb_ivar_set(res, id_iv_cas, cas);
68
+ cb_proc_call(ctx->proc, 1, res);
69
+ }
70
+ } else { /* synchronous */
71
+ if (NIL_P(exc) && error != LCB_KEY_ENOENT) {
72
+ if (ctx->extended) {
73
+ rb_hash_aset(*rv, key, rb_ary_new3(3, val, flags, cas));
74
+ } else {
75
+ rb_hash_aset(*rv, key, val);
76
+ }
77
+ }
78
+ }
79
+
80
+ if (ctx->nqueries == 0) {
81
+ cb_gc_unprotect(bucket, ctx->proc);
82
+ }
83
+ (void)handle;
84
+ }
85
+
86
+ /*
87
+ * Obtain an object stored in Couchbase by given key.
88
+ *
89
+ * @since 1.0.0
90
+ *
91
+ * @see http://couchbase.com/docs/couchbase-manual-2.0/couchbase-architecture-apis-memcached-protocol-additions.html#couchbase-architecture-apis-memcached-protocol-additions-getl
92
+ *
93
+ * @overload get(*keys, options = {})
94
+ * @param keys [String, Symbol, Array] One or several keys to fetch
95
+ * @param options [Hash] Options for operation.
96
+ * @option options [true, false] :extended (false) If set to +true+, the
97
+ * operation will return tuple +[value, flags, cas]+, otherwise (by
98
+ * default) it returns just value.
99
+ * @option options [Fixnum] :ttl (self.default_ttl) Expiry time for key.
100
+ * Values larger than 30*24*60*60 seconds (30 days) are interpreted as
101
+ * absolute times (from the epoch).
102
+ * @option options [true, false] :quiet (self.quiet) If set to +true+, the
103
+ * operation won't raise error for missing key, it will return +nil+.
104
+ * Otherwise it will raise error in synchronous mode. In asynchronous
105
+ * mode this option ignored.
106
+ * @option options [Symbol] :format (nil) Explicitly choose the decoder
107
+ * for this key (+:plain+, +:document+, +:marshal+). See
108
+ * {Bucket#default_format}.
109
+ * @option options [Fixnum, Boolean] :lock Lock the keys for time span.
110
+ * If this parameter is +true+ the key(s) will be locked for default
111
+ * timeout. Also you can use number to setup your own timeout in
112
+ * seconds. If it will be lower that zero or exceed the maximum, the
113
+ * server will use default value. You can determine actual default and
114
+ * maximum values calling {Bucket#stats} without arguments and
115
+ * inspecting keys "ep_getl_default_timeout" and "ep_getl_max_timeout"
116
+ * correspondingly. See overloaded hash syntax to specify custom timeout
117
+ * per each key.
118
+ * @option options [true, false] :assemble_hash (false) Assemble Hash for
119
+ * results. Hash assembled automatically if +:extended+ option is true
120
+ * or in case of "get and touch" multimple keys.
121
+ * @option options [true, false] :replica (false) Read key from replica
122
+ * node. Options +:ttl+ and +:lock+ are not compatible with +:replica+.
123
+ *
124
+ * @yieldparam ret [Result] the result of operation in asynchronous mode
125
+ * (valid attributes: +error+, +operation+, +key+, +value+, +flags+,
126
+ * +cas+).
127
+ *
128
+ * @return [Object, Array, Hash] the value(s) (or tuples in extended mode)
129
+ * assiciated with the key.
130
+ *
131
+ * @raise [Couchbase::Error::NotFound] if the key is missing in the
132
+ * bucket.
133
+ *
134
+ * @raise [Couchbase::Error::Connect] if connection closed (see {Bucket#reconnect})
135
+ *
136
+ * @raise [ArgumentError] when passing the block in synchronous mode
137
+ *
138
+ * @example Get single value in quite mode (the default)
139
+ * c.get("foo") #=> the associated value or nil
140
+ *
141
+ * @example Use alternative hash-like syntax
142
+ * c["foo"] #=> the associated value or nil
143
+ *
144
+ * @example Get single value in verbose mode
145
+ * c.get("missing-foo", :quiet => false) #=> raises Couchbase::NotFound
146
+ * c.get("missing-foo", :quiet => true) #=> returns nil
147
+ *
148
+ * @example Get and touch single value. The key won't be accessible after 10 seconds
149
+ * c.get("foo", :ttl => 10)
150
+ *
151
+ * @example Extended get
152
+ * val, flags, cas = c.get("foo", :extended => true)
153
+ *
154
+ * @example Get multiple keys
155
+ * c.get("foo", "bar", "baz") #=> [val1, val2, val3]
156
+ *
157
+ * @example Get multiple keys with assembing result into the Hash
158
+ * c.get("foo", "bar", "baz", :assemble_hash => true)
159
+ * #=> {"foo" => val1, "bar" => val2, "baz" => val3}
160
+ *
161
+ * @example Extended get multiple keys
162
+ * c.get("foo", "bar", :extended => true)
163
+ * #=> {"foo" => [val1, flags1, cas1], "bar" => [val2, flags2, cas2]}
164
+ *
165
+ * @example Asynchronous get
166
+ * c.run do
167
+ * c.get("foo", "bar", "baz") do |res|
168
+ * ret.operation #=> :get
169
+ * ret.success? #=> true
170
+ * ret.key #=> "foo", "bar" or "baz" in separate calls
171
+ * ret.value
172
+ * ret.flags
173
+ * ret.cas
174
+ * end
175
+ * end
176
+ *
177
+ * @example Get and lock key using default timeout
178
+ * c.get("foo", :lock => true)
179
+ *
180
+ * @example Determine lock timeout parameters
181
+ * c.stats.values_at("ep_getl_default_timeout", "ep_getl_max_timeout")
182
+ * #=> [{"127.0.0.1:11210"=>"15"}, {"127.0.0.1:11210"=>"30"}]
183
+ *
184
+ * @example Get and lock key using custom timeout
185
+ * c.get("foo", :lock => 3)
186
+ *
187
+ * @example Get and lock multiple keys using custom timeout
188
+ * c.get("foo", "bar", :lock => 3)
189
+ *
190
+ * @overload get(keys, options = {})
191
+ * When the method receive hash map, it will behave like it receive list
192
+ * of keys (+keys.keys+), but also touch each key setting expiry time to
193
+ * the corresponding value. But unlike usual get this command always
194
+ * return hash map +{key => value}+ or +{key => [value, flags, cas]}+.
195
+ *
196
+ * @param keys [Hash] Map key-ttl
197
+ * @param options [Hash] Options for operation. (see options definition
198
+ * above)
199
+ *
200
+ * @return [Hash] the values (or tuples in extended mode) assiciated with
201
+ * the keys.
202
+ *
203
+ * @example Get and touch multiple keys
204
+ * c.get("foo" => 10, "bar" => 20) #=> {"foo" => val1, "bar" => val2}
205
+ *
206
+ * @example Extended get and touch multiple keys
207
+ * c.get({"foo" => 10, "bar" => 20}, :extended => true)
208
+ * #=> {"foo" => [val1, flags1, cas1], "bar" => [val2, flags2, cas2]}
209
+ *
210
+ * @example Get and lock multiple keys for chosen period in seconds
211
+ * c.get("foo" => 10, "bar" => 20, :lock => true)
212
+ * #=> {"foo" => val1, "bar" => val2}
213
+ */
214
+ VALUE
215
+ cb_bucket_get(int argc, VALUE *argv, VALUE self)
216
+ {
217
+ struct bucket_st *bucket = DATA_PTR(self);
218
+ struct context_st *ctx;
219
+ VALUE args, rv, proc, exc;
220
+ size_t ii;
221
+ lcb_error_t err = LCB_SUCCESS;
222
+ struct params_st params;
223
+
224
+ if (bucket->handle == NULL) {
225
+ rb_raise(eConnectError, "closed connection");
226
+ }
227
+ rb_scan_args(argc, argv, "0*&", &args, &proc);
228
+ if (!bucket->async && proc != Qnil) {
229
+ rb_raise(rb_eArgError, "synchronous mode doesn't support callbacks");
230
+ }
231
+ memset(&params, 0, sizeof(struct params_st));
232
+ params.type = cmd_get;
233
+ params.bucket = bucket;
234
+ params.cmd.get.keys_ary = cb_gc_protect(bucket, rb_ary_new());
235
+ cb_params_build(&params, RARRAY_LEN(args), args);
236
+ ctx = xcalloc(1, sizeof(struct context_st));
237
+ if (ctx == NULL) {
238
+ rb_raise(eClientNoMemoryError, "failed to allocate memory for context");
239
+ }
240
+ ctx->extended = params.cmd.get.extended;
241
+ ctx->quiet = params.cmd.get.quiet;
242
+ ctx->force_format = params.cmd.get.forced_format;
243
+ ctx->proc = cb_gc_protect(bucket, proc);
244
+ ctx->bucket = bucket;
245
+ rv = rb_hash_new();
246
+ ctx->rv = &rv;
247
+ ctx->exception = Qnil;
248
+ ctx->nqueries = params.cmd.get.num;
249
+ if (params.cmd.get.replica) {
250
+ err = lcb_get_replica(bucket->handle, (const void *)ctx,
251
+ params.cmd.get.num, params.cmd.get.ptr_gr);
252
+ } else {
253
+ err = lcb_get(bucket->handle, (const void *)ctx,
254
+ params.cmd.get.num, params.cmd.get.ptr);
255
+ }
256
+ cb_params_destroy(&params);
257
+ cb_gc_unprotect(bucket, params.cmd.get.keys_ary);
258
+ exc = cb_check_error(err, "failed to schedule get request", Qnil);
259
+ if (exc != Qnil) {
260
+ xfree(ctx);
261
+ rb_exc_raise(exc);
262
+ }
263
+ bucket->nbytes += params.npayload;
264
+ if (bucket->async) {
265
+ maybe_do_loop(bucket);
266
+ return Qnil;
267
+ } else {
268
+ if (ctx->nqueries > 0) {
269
+ /* we have some operations pending */
270
+ lcb_wait(bucket->handle);
271
+ }
272
+ exc = ctx->exception;
273
+ xfree(ctx);
274
+ if (exc != Qnil) {
275
+ cb_gc_unprotect(bucket, exc);
276
+ rb_exc_raise(exc);
277
+ }
278
+ if (bucket->exception != Qnil) {
279
+ rb_exc_raise(bucket->exception);
280
+ }
281
+ if (params.cmd.get.gat || params.cmd.get.assemble_hash ||
282
+ (params.cmd.get.extended && (params.cmd.get.num > 1 || params.cmd.get.array))) {
283
+ return rv; /* return as a hash {key => [value, flags, cas], ...} */
284
+ }
285
+ if (params.cmd.get.num > 1 || params.cmd.get.array) {
286
+ VALUE *keys_ptr, ret;
287
+ ret = rb_ary_new();
288
+ keys_ptr = RARRAY_PTR(params.cmd.get.keys_ary);
289
+ for (ii = 0; ii < params.cmd.get.num; ++ii) {
290
+ rb_ary_push(ret, rb_hash_aref(rv, keys_ptr[ii]));
291
+ }
292
+ return ret; /* return as an array [value1, value2, ...] */
293
+ } else {
294
+ VALUE vv = Qnil;
295
+ rb_hash_foreach(rv, cb_first_value_i, (VALUE)&vv);
296
+ return vv;
297
+ }
298
+ }
299
+ }
300
+
301
+