couchbase 1.0.0 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore CHANGED
@@ -7,6 +7,8 @@ core*
7
7
  doc/
8
8
  ext/couchbase/Makefile
9
9
  ext/couchbase/mkmf.log
10
+ lib/couchbase_ext.rb
10
11
  pkg/*
12
+ ports
11
13
  test/CouchbaseMock.jar
12
14
  tmp
@@ -0,0 +1,11 @@
1
+ before_install:
2
+ - wget -O- http://packages.couchbase.com/ubuntu/couchbase.key | sudo apt-key add -
3
+ - echo deb http://packages.couchbase.com/ubuntu lucid lucid/main | sudo tee /etc/apt/sources.list.d/couchbase.list
4
+ - sudo apt-get update
5
+ - sudo apt-get -y install libevent-dev libvbucket-dev libcouchbase-dev
6
+
7
+ rvm:
8
+ - 1.8.7
9
+ - 1.9.2
10
+ - 1.9.3
11
+ - ree
@@ -1,3 +1,20 @@
1
+ ## 1.1.0 / 2012-03-07
2
+
3
+ * Timeout support (CCBC-20)
4
+ * Implement disconnect/reconnect interface
5
+ * Improve error handling code
6
+ * Use URI parser from stdlib
7
+ * Improve the documentation and the tests
8
+ * Remove direct dependency on libevent
9
+ * Remove sasl dependency
10
+ * Fix storing empty line and nil
11
+ * Allow running tests on real cluster
12
+ * Cross-build for windows
13
+ * Keep connections in thread local storage
14
+ * Add block execution time to timeout
15
+ * Implement VERSION command
16
+ * Configure Travis-CI
17
+
1
18
  ## 1.0.0 / 2011-12-23
2
19
 
3
20
  * Implement all operations using libcouchbase as backend
@@ -1,59 +1,67 @@
1
- # Couchbase Ruby Client
1
+ # Couchbase Ruby Client [![Build Status](https://secure.travis-ci.org/avsej/couchbase-ruby-client.png?branch=master)](http://travis-ci.org/avsej/couchbase-ruby-client)
2
2
 
3
3
  This is the official client library for use with Couchbase Server.
4
4
 
5
- ## INSTALL
5
+ ## SUPPORT
6
+
7
+ If you found an issue, please file it in our [JIRA][1]. Also you are
8
+ always welcome on `#libcouchbase` channel at [freenode.net IRC servers][2].
6
9
 
7
- This gem depends on a couple of external libraries to work with JSON and
8
- Couchbase Server. For JSON it uses [yajl-ruby][1] which is built atop
9
- [yajl][2]. For Couchbase iteraction it uses [libcouchbase][3]. The first
10
- dependency shouldn't cause any issues because it will bundle yajl in the c
11
- extensions. To install yajl-ruby use following command:
10
+ Documentation: [http://rdoc.info/gems/couchbase](http://rdoc.info/gems/couchbase)
12
11
 
13
- $ gem install yajl-ruby
12
+ ## INSTALL
14
13
 
15
- In most cases installing libcouchbase is just as simple.
14
+ This gem depends [libcouchbase][3]. In most cases installing
15
+ libcouchbase doesn't take much effort.
16
16
 
17
17
  ### MacOS (Homebrew)
18
18
 
19
19
  $ brew install libcouchbase
20
20
 
21
- Or if our pull request isn't yet merged:
21
+ Or if [our pull requests][4] for isn't yet merged:
22
22
 
23
- $ brew install http://packages.couchbase.com/clients/c/homebrew/libvbucket.rb
24
- $ brew install http://packages.couchbase.com/clients/c/homebrew/libcouchbase.rb
23
+ $ brew install https://github.com/avsej/homebrew/raw/libvbucket/Library/Formula/libvbucket.rb
24
+ $ brew install https://github.com/avsej/homebrew/raw/libcouchbase/Library/Formula/libcouchbase.rb
25
25
 
26
26
  ### Debian (Ubuntu)
27
27
 
28
- Download packages depending on your architecture:
28
+ Add the appropriate line to /etc/apt/sources.list.d/couchbase.list for
29
+ your OS release:
29
30
 
30
- $ wget http://packages.couchbase.com/clients/c/libvbucket{1,1-dbg,-dev}_1.8.0.1-1_amd64.deb
31
- $ wget http://packages.couchbase.com/clients/c/libcouchbase{1,1-dbg,-dev}_1.0.0-1_amd64.deb
31
+ # Ubuntu 11.10 Oneiric Ocelot (Debian unstable)
32
+ deb http://packages.couchbase.com/ubuntu oneiric oneiric/main
32
33
 
33
- or
34
+ # Ubuntu 10.04 Lucid Lynx (Debian stable or testing)
35
+ deb http://packages.couchbase.com/ubuntu lucid lucid/main
34
36
 
35
- $ wget http://packages.couchbase.com/clients/c/libvbucket{1,1-dbg,-dev}_1.8.0.1-1_i386.deb
36
- $ wget http://packages.couchbase.com/clients/c/libcouchbase{1,1-dbg,-dev}_1.0.0-1_i386.deb
37
+ Import Couchbase PGP key:
37
38
 
38
- Then install them using dpkg tool
39
+ wget -O- http://packages.couchbase.com/ubuntu/couchbase.key | sudo apt-key add -
39
40
 
40
- $ sudo dpkg -i lib{vbucket,couchbase}*.deb
41
+ Then install them
42
+
43
+ $ sudo apt-get update && sudo apt-get install libcouchbase-dev
41
44
 
42
45
  ### Centos (Redhat and rpm-based systems)
43
46
 
44
- Download packages depending on your architecture:
47
+ Add these lines to /etc/yum.repos.d/couchbase.repo using the correct architecture
48
+
49
+ [couchbase]
50
+ name = Couchbase package repository
51
+ baseurl = http://packages.couchbase.com/rpm/5.5/i386
45
52
 
46
- $ wget http://packages.couchbase.com/clients/c/libvbucket{1,-debuginfo,-devel}-1.8.0.1-1.x86_64.rpm
47
- $ wget http://packages.couchbase.com/clients/c/libcouchbase{1,-debuginfo,-devel}-1.0.0-1.x86_64.rpm
53
+ [couchbase]
54
+ name = Couchbase package repository
55
+ baseurl = http:///packages.couchbase.com/rpm/5.5/x86_64
48
56
 
49
- or
57
+ Then to install libcouchbase itself, run:
50
58
 
51
- $ wget http://packages.couchbase.com/clients/c/libvbucket{1,-debuginfo,-devel}-1.8.0.1-1.i386.rpm
52
- $ wget http://packages.couchbase.com/clients/c/libcouchbase{1,-debuginfo,-devel}-1.0.0-1.i386.rpm
59
+ $ sudo yum update && sudo yum install libcouchbase-devel
53
60
 
54
- Then install them using rpm tool
61
+ ### Windows
55
62
 
56
- $ sudo rpm -ivh lib{vbucket,couchbase}*.rpm
63
+ There no additional dependencies for Windows systems. The gem carry
64
+ prebuilt binary for it.
57
65
 
58
66
  ### Couchbase gem
59
67
 
@@ -61,7 +69,6 @@ Now install the couchbase gem itself
61
69
 
62
70
  $ gem install couchbase
63
71
 
64
-
65
72
  ## USAGE
66
73
 
67
74
  First of all you need to load library:
@@ -87,20 +94,13 @@ This is equivalent to following forms:
87
94
 
88
95
  The hash parameters take precedence on string URL.
89
96
 
90
- The library supports both synchronous and asynchronous mode. You can
91
- choose either using the `:async` option or attribute.
92
-
93
- c = Couchbase.new(:async => true)
94
- # ... asynchronous mode
95
- c.async = false
96
- # ... synchronous mode
97
-
98
- In asynchronous mode all operations will return control to caller
97
+ The library supports both synchronous and asynchronous mode. In
98
+ asynchronous mode all operations will return control to caller
99
99
  without blocking current thread. You can pass the block to method and it
100
100
  will be called with result when the operation will be completed. You
101
101
  need to run event loop when you scheduled your operations:
102
102
 
103
- c = Couchbase.new(:async => true)
103
+ c = Couchbase.new
104
104
  c.run do |conn|
105
105
  conn.get("foo") {|ret| puts ret.value}
106
106
  conn.set("bar", "baz")
@@ -139,10 +139,10 @@ To handle global errors in async mode `#on_error` callback should be
139
139
  used. It can be set in following fashions:
140
140
 
141
141
  c.on_error do |opcode, key, exc|
142
- ...
142
+ # ...
143
143
  end
144
144
 
145
- handler = lambda {|opcode, key, exc| ...}
145
+ handler = lambda {|opcode, key, exc| }
146
146
  c.on_error = handler
147
147
 
148
148
  By default connection uses `:quiet` mode. This mean it won't raise
@@ -156,7 +156,7 @@ You can turn on these exception by passing `:quiet => false` when you
156
156
  are instantiating the connection or change corresponding attribute:
157
157
 
158
158
  c.quiet = false
159
- c.get("missing-key") #=> raise Couchbase::Error::NotFound
159
+ c.get("missing-key") #=> raise Couchbase::Error::NotFound
160
160
  c.get("missing-key", :quiet => true) #=> nil
161
161
 
162
162
  The library supports three different formats for representing values:
@@ -174,7 +174,7 @@ The library supports three different formats for representing values:
174
174
  * `:plain` Use this format if you'd like to transparently serialize your
175
175
  ruby object with standard `Marshal.dump` and `Marshal.load` methods
176
176
 
177
- The couchbase API is the superset of [Memcached binary protocol][4], so
177
+ The couchbase API is the superset of [Memcached binary protocol][5], so
178
178
  you can use its operations.
179
179
 
180
180
  ### Get
@@ -190,8 +190,17 @@ Get multiple values. In quiet mode will put `nil` values on missing
190
190
  positions:
191
191
 
192
192
  vals = c.get("foo", "bar", "baz")
193
- c.get("foo"){|val, key| ... }
194
- c.get("foo", :extended => true){|val, key, flags, cas| ... }
193
+ val_foo, val_bar, val_baz = c.get("foo", "bar", "baz")
194
+ c.run do
195
+ c.get("foo") do |ret|
196
+ ret.success?
197
+ ret.error
198
+ ret.key
199
+ ret.value
200
+ ret.flags
201
+ ret.cas
202
+ end
203
+ end
195
204
 
196
205
  Get multiple values with extended information. The result will
197
206
  represented by hash with tuples `[value, flags, cas]` as a value.
@@ -214,7 +223,7 @@ Hash-like syntax
214
223
  c.touch("foo", 10)
215
224
  c.touch("foo", :ttl => 10)
216
225
  c.touch("foo" => 10, "bar" => 20)
217
- c.touch("foo" => 10, "bar" => 20){|key, success| ... }
226
+ c.touch("foo" => 10, "bar" => 20){|key, success| }
218
227
 
219
228
  ### Set
220
229
 
@@ -222,9 +231,9 @@ Hash-like syntax
222
231
  c.set("foo", "bar", :flags => 0x1000, :ttl => 30, :format => :plain)
223
232
  c["foo"] = "bar"
224
233
  c["foo", {:flags => 0x1000, :format => :plain}] = "bar"
225
- c["foo", :flags => 0x1000] = "bar" # for ruby 1.9.x only
234
+ c["foo", :flags => 0x1000] = "bar" # for ruby 1.9.x only
226
235
  c.set("foo", "bar", :cas => 8835713818674332672)
227
- c.set("foo", "bar"){|cas, key, operation| ... }
236
+ c.set("foo", "bar"){|cas, key, operation| }
228
237
 
229
238
  ### Add
230
239
 
@@ -264,12 +273,24 @@ Couchbase::Error::DeltaBadval if the delta or value is not a number.
264
273
  c.incr("foo", 4) #=> 8
265
274
  c.incr("foo", -1) #=> 7
266
275
  c.incr("foo", -100) #=> 0
267
- c.incr("foo"){|val, cas| ... }
276
+ c.run do
277
+ c.incr("foo") do |ret|
278
+ ret.success?
279
+ ret.value
280
+ ret.cas
281
+ end
282
+ end
268
283
 
269
284
  c.set("foo", 10)
270
285
  c.decr("foo", 1) #=> 9
271
286
  c.decr("foo", 100) #=> 0
272
- c.decr("foo"){|val, cas| ... }
287
+ c.run do
288
+ c.decr("foo") do |ret|
289
+ ret.success?
290
+ ret.value
291
+ ret.cas
292
+ end
293
+ end
273
294
 
274
295
  c.incr("missing1", :initial => 10) #=> 10
275
296
  c.incr("missing1", :initial => 10) #=> 11
@@ -287,21 +308,39 @@ performed on client side with following `set` operation:
287
308
  c.delete("foo")
288
309
  c.delete("foo", :cas => 8835713818674332672)
289
310
  c.delete("foo", 8835713818674332672)
290
- c.delete{|key, success| ... }
311
+ c.run do
312
+ c.delete do |ret|
313
+ ret.success?
314
+ ret.key
315
+ end
316
+ end
291
317
 
292
318
  ### Flush
293
319
 
294
320
  Flush the items in the cluster.
295
321
 
296
322
  c.flush
297
- c.flush{|node, success| ... }
323
+ c.run do
324
+ c.flush do |ret|
325
+ ret.success?
326
+ ret.node
327
+ end
328
+ end
298
329
 
299
330
  ### Stats
300
331
 
301
332
  Return statistics from each node in the cluster
302
333
 
303
334
  c.stats
304
- c.stats{|node, key, value| ... }
335
+ c.stats(:memory)
336
+ c.run do
337
+ c.stats do |ret|
338
+ ret.success?
339
+ ret.node
340
+ ret.key
341
+ ret.value
342
+ end
343
+ end
305
344
 
306
345
  The result is represented as a hash with the server node address as
307
346
  the key and stats as key-value pairs.
@@ -312,19 +351,20 @@ the key and stats as key-value pairs.
312
351
  "threads"=>"4",
313
352
  "connection_structures"=>"22",
314
353
  "ep_max_txn_size"=>"10000",
315
- ...
354
+ # ...
316
355
  },
317
356
  "172.16.16.76:12000"=>
318
357
  {
319
358
  "threads"=>"4",
320
359
  "connection_structures"=>"447",
321
360
  "ep_max_txn_size"=>"10000",
322
- ...
361
+ # ...
323
362
  },
324
- ...
363
+ # ...
325
364
  }
326
365
 
327
- [1]: https://github.com/brianmario/yajl-ruby/
328
- [2]: http://lloyd.github.com/yajl/
366
+ [1]: http://couchbase.com/issues/browse/RCBC
367
+ [2]: http://freenode.net/irc_servers.shtml
329
368
  [3]: http://www.couchbase.com/develop/c/current
330
- [4]: http://code.google.com/p/memcached/wiki/BinaryProtocolRevamped
369
+ [4]: https://github.com/mxcl/homebrew/pulls/avsej
370
+ [5]: http://code.google.com/p/memcached/wiki/BinaryProtocolRevamped
@@ -42,5 +42,6 @@ Gem::Specification.new do |s|
42
42
  s.add_development_dependency 'rake-compiler', '>= 0.7.5'
43
43
  s.add_development_dependency 'rdiscount'
44
44
  s.add_development_dependency 'yard'
45
+ s.add_development_dependency 'mini_portile'
45
46
  s.add_development_dependency RUBY_VERSION =~ /^1\.9/ ? 'ruby-debug19' : 'ruby-debug'
46
47
  end
@@ -20,8 +20,8 @@
20
20
  #include <st.h>
21
21
  #endif
22
22
 
23
+ #include <time.h>
23
24
  #include <libcouchbase/couchbase.h>
24
- #include <event.h>
25
25
  #include "couchbase_config.h"
26
26
 
27
27
  #ifdef HAVE_STDARG_PROTOTYPES
@@ -60,7 +60,8 @@ typedef struct
60
60
  long seqno;
61
61
  VALUE default_format; /* should update +default_flags+ on change */
62
62
  uint32_t default_flags;
63
- uint32_t default_ttl;
63
+ time_t default_ttl;
64
+ uint32_t timeout;
64
65
  VALUE exception; /* error delivered by error_callback */
65
66
  VALUE on_error_proc; /* is using to deliver errors in async mode */
66
67
  } bucket_t;
@@ -81,14 +82,15 @@ struct key_traits
81
82
  VALUE keys_ary;
82
83
  size_t nkeys;
83
84
  char **keys;
84
- size_t *lens;
85
+ libcouchbase_size_t *lens;
85
86
  time_t *ttls;
86
87
  int extended;
87
88
  int explicit_ttl;
88
89
  int quiet;
90
+ int mgat;
89
91
  };
90
92
 
91
- static VALUE mCouchbase, mError, mJSON, mMarshal, cBucket, cResult;
93
+ static VALUE mCouchbase, mError, mJSON, mURI, mMarshal, cBucket, cResult;
92
94
  static VALUE object_space;
93
95
 
94
96
  static ID sym_add,
@@ -99,6 +101,7 @@ static ID sym_add,
99
101
  sym_decrement,
100
102
  sym_default_flags,
101
103
  sym_default_format,
104
+ sym_default_ttl,
102
105
  sym_delete,
103
106
  sym_document,
104
107
  sym_extended,
@@ -119,35 +122,33 @@ static ID sym_add,
119
122
  sym_replace,
120
123
  sym_set,
121
124
  sym_stats,
125
+ sym_timeout,
122
126
  sym_touch,
123
127
  sym_ttl,
124
128
  sym_username,
129
+ sym_version,
125
130
  id_arity,
126
131
  id_call,
127
132
  id_dump,
128
133
  id_flatten_bang,
129
134
  id_has_key_p,
130
- id_load,
131
- id_to_s,
132
- id_iv_authority,
133
- id_iv_bucket,
135
+ id_host,
134
136
  id_iv_cas,
135
- id_iv_default_flags,
136
- id_iv_default_format,
137
137
  id_iv_error,
138
138
  id_iv_flags,
139
- id_iv_hostname,
140
139
  id_iv_key,
141
140
  id_iv_node,
142
- id_iv_on_error,
143
141
  id_iv_operation,
144
- id_iv_password,
145
- id_iv_pool,
146
- id_iv_port,
147
- id_iv_quiet,
148
- id_iv_url,
149
- id_iv_username,
150
- id_iv_value;
142
+ id_iv_value,
143
+ id_load,
144
+ id_match,
145
+ id_parse,
146
+ id_password,
147
+ id_path,
148
+ id_port,
149
+ id_scheme,
150
+ id_to_s,
151
+ id_user;
151
152
 
152
153
  /* base error */
153
154
  static VALUE eBaseError;
@@ -176,6 +177,9 @@ static VALUE eNotSupportedError; /*LIBCOUCHBASE_NOT_SUPPORTED = 0x12*/
176
177
  static VALUE eUnknownCommandError; /*LIBCOUCHBASE_UNKNOWN_COMMAND = 0x13*/
177
178
  static VALUE eUnknownHostError; /*LIBCOUCHBASE_UNKNOWN_HOST = 0x14*/
178
179
  static VALUE eProtocolError; /*LIBCOUCHBASE_PROTOCOL_ERROR = 0x15*/
180
+ static VALUE eTimeoutError; /*LIBCOUCHBASE_ETIMEDOUT = 0x16*/
181
+ static VALUE eConnectError; /*LIBCOUCHBASE_CONNECT_ERROR = 0x17*/
182
+ static VALUE eBucketNotFoundError; /*LIBCOUCHBASE_BUCKET_ENOENT = 0x18*/
179
183
 
180
184
  static VALUE
181
185
  cb_proc_call(VALUE recv, int argc, ...)
@@ -274,6 +278,16 @@ cb_check_error(libcouchbase_error_t rc, const char *msg, VALUE key)
274
278
  break;
275
279
  case LIBCOUCHBASE_PROTOCOL_ERROR:
276
280
  klass = eProtocolError;
281
+ break;
282
+ case LIBCOUCHBASE_ETIMEDOUT:
283
+ klass = eTimeoutError;
284
+ break;
285
+ case LIBCOUCHBASE_CONNECT_ERROR:
286
+ klass = eConnectError;
287
+ break;
288
+ case LIBCOUCHBASE_BUCKET_ENOENT:
289
+ klass = eBucketNotFoundError;
290
+ break;
277
291
  case LIBCOUCHBASE_ERROR:
278
292
  /* fall through */
279
293
  default:
@@ -286,7 +300,7 @@ cb_check_error(libcouchbase_error_t rc, const char *msg, VALUE key)
286
300
  snprintf(buf, 300, "key=\"%s\", ", RSTRING_PTR(key));
287
301
  rb_str_buf_cat2(str, buf);
288
302
  }
289
- snprintf(buf, 300, "error=0x%x)", rc);
303
+ snprintf(buf, 300, "error=0x%02x)", rc);
290
304
  rb_str_buf_cat2(str, buf);
291
305
  exc = rb_exc_new3(klass, str);
292
306
  rb_ivar_set(exc, id_iv_error, INT2FIX(rc));
@@ -433,44 +447,20 @@ cb_extract_keys_i(VALUE key, VALUE value, VALUE arg)
433
447
  static long
434
448
  cb_args_scan_keys(long argc, VALUE argv, bucket_t *bucket, struct key_traits *traits)
435
449
  {
436
- VALUE arg, key, *keys_ptr, opts, ttl, ext;
450
+ VALUE key, *keys_ptr, opts, ttl, ext;
437
451
  long nn = 0, ii;
438
452
  time_t exp;
439
453
 
440
454
  traits->keys_ary = rb_ary_new();
441
455
  traits->quiet = bucket->quiet;
442
- if (argc == 1) {
443
- arg = RARRAY_PTR(argv)[0];
444
- switch (TYPE(arg)) {
445
- case T_HASH:
446
- /* hash of key-ttl pairs */
447
- nn = RHASH_SIZE(arg);
448
- traits->keys = calloc(nn, sizeof(char *));
449
- traits->lens = calloc(nn, sizeof(size_t));
450
- traits->explicit_ttl = 1;
451
- traits->ttls = calloc(nn, sizeof(time_t));
452
- rb_hash_foreach(arg, cb_extract_keys_i, (VALUE)traits);
453
- break;
454
- case T_STRING:
455
- case T_SYMBOL:
456
- /* single key with default expiration */
457
- nn = traits->nkeys = 1;
458
- traits->keys = calloc(nn, sizeof(char *));
459
- traits->lens = calloc(nn, sizeof(size_t));
460
- traits->ttls = calloc(nn, sizeof(time_t));
461
- key = unify_key(arg);
462
- rb_ary_push(traits->keys_ary, key);
463
- traits->keys[0] = RSTRING_PTR(key);
464
- traits->lens[0] = RSTRING_LEN(key);
465
- traits->ttls[0] = bucket->default_ttl;
466
- break;
467
- }
468
- } else if (argc > 1) {
456
+ traits->mgat = 0;
457
+
458
+ if (argc > 0) {
469
459
  /* keys with custom options */
470
460
  opts = RARRAY_PTR(argv)[argc-1];
471
461
  exp = bucket->default_ttl;
472
462
  ext = Qfalse;
473
- if (TYPE(opts) == T_HASH) {
463
+ if (argc > 1 && TYPE(opts) == T_HASH) {
474
464
  (void)rb_ary_pop(argv);
475
465
  if (RTEST(rb_funcall(opts, id_has_key_p, 1, sym_quiet))) {
476
466
  traits->quiet = RTEST(rb_hash_aref(opts, sym_quiet));
@@ -488,18 +478,30 @@ cb_args_scan_keys(long argc, VALUE argv, bucket_t *bucket, struct key_traits *tr
488
478
  if (nn < 1) {
489
479
  rb_raise(rb_eArgError, "must be at least one key");
490
480
  }
491
- traits->nkeys = nn;
492
- traits->extended = RTEST(ext) ? 1 : 0;
493
- traits->keys = calloc(nn, sizeof(char *));
494
- traits->lens = calloc(nn, sizeof(size_t));
495
- traits->ttls = calloc(nn, sizeof(time_t));
496
481
  keys_ptr = RARRAY_PTR(argv);
497
- for (ii = 0; ii < nn; ii++) {
498
- key = unify_key(keys_ptr[ii]);
499
- rb_ary_push(traits->keys_ary, key);
500
- traits->keys[ii] = RSTRING_PTR(key);
501
- traits->lens[ii] = RSTRING_LEN(key);
502
- traits->ttls[ii] = exp;
482
+ traits->extended = RTEST(ext) ? 1 : 0;
483
+ if (nn == 1 && TYPE(keys_ptr[0]) == T_HASH) {
484
+ /* hash of key-ttl pairs */
485
+ nn = RHASH_SIZE(keys_ptr[0]);
486
+ traits->keys = calloc(nn, sizeof(char *));
487
+ traits->lens = calloc(nn, sizeof(size_t));
488
+ traits->explicit_ttl = 1;
489
+ traits->mgat = 1;
490
+ traits->ttls = calloc(nn, sizeof(time_t));
491
+ rb_hash_foreach(keys_ptr[0], cb_extract_keys_i, (VALUE)traits);
492
+ } else {
493
+ /* the list of keys */
494
+ traits->nkeys = nn;
495
+ traits->keys = calloc(nn, sizeof(char *));
496
+ traits->lens = calloc(nn, sizeof(size_t));
497
+ traits->ttls = calloc(nn, sizeof(time_t));
498
+ for (ii = 0; ii < nn; ii++) {
499
+ key = unify_key(keys_ptr[ii]);
500
+ rb_ary_push(traits->keys_ary, key);
501
+ traits->keys[ii] = RSTRING_PTR(key);
502
+ traits->lens[ii] = RSTRING_LEN(key);
503
+ traits->ttls[ii] = exp;
504
+ }
503
505
  }
504
506
  }
505
507
 
@@ -518,7 +520,7 @@ error_callback(libcouchbase_t handle, libcouchbase_error_t error, const char *er
518
520
  static void
519
521
  storage_callback(libcouchbase_t handle, const void *cookie,
520
522
  libcouchbase_storage_t operation, libcouchbase_error_t error,
521
- const void *key, size_t nkey, uint64_t cas)
523
+ const void *key, libcouchbase_size_t nkey, libcouchbase_cas_t cas)
522
524
  {
523
525
  context_t *ctx = (context_t *)cookie;
524
526
  bucket_t *bucket = ctx->bucket;
@@ -578,7 +580,8 @@ storage_callback(libcouchbase_t handle, const void *cookie,
578
580
 
579
581
  static void
580
582
  delete_callback(libcouchbase_t handle, const void *cookie,
581
- libcouchbase_error_t error, const void *key, size_t nkey)
583
+ libcouchbase_error_t error, const void *key,
584
+ libcouchbase_size_t nkey)
582
585
  {
583
586
  context_t *ctx = (context_t *)cookie;
584
587
  bucket_t *bucket = ctx->bucket;
@@ -616,8 +619,10 @@ delete_callback(libcouchbase_t handle, const void *cookie,
616
619
 
617
620
  static void
618
621
  get_callback(libcouchbase_t handle, const void *cookie,
619
- libcouchbase_error_t error, const void *key, size_t nkey,
620
- const void *bytes, size_t nbytes, uint32_t flags, uint64_t cas)
622
+ libcouchbase_error_t error, const void *key,
623
+ libcouchbase_size_t nkey, const void *bytes,
624
+ libcouchbase_size_t nbytes, libcouchbase_uint32_t flags,
625
+ libcouchbase_cas_t cas)
621
626
  {
622
627
  context_t *ctx = (context_t *)cookie;
623
628
  bucket_t *bucket = ctx->bucket;
@@ -645,7 +650,11 @@ get_callback(libcouchbase_t handle, const void *cookie,
645
650
  v = Qnil;
646
651
  }
647
652
  } else {
648
- v = Qnil;
653
+ if (flags_get_format(flags) == sym_plain) {
654
+ v = rb_str_new2("");
655
+ } else {
656
+ v = Qnil;
657
+ }
649
658
  }
650
659
  if (bucket->async) { /* asynchronous */
651
660
  if (ctx->proc != Qnil) {
@@ -659,7 +668,7 @@ get_callback(libcouchbase_t handle, const void *cookie,
659
668
  cb_proc_call(ctx->proc, 1, res);
660
669
  }
661
670
  } else { /* synchronous */
662
- if (NIL_P(exc) && v != Qnil) {
671
+ if (NIL_P(exc) && error != LIBCOUCHBASE_KEY_ENOENT) {
663
672
  if (ctx->extended) {
664
673
  rb_hash_aset(*rv, k, rb_ary_new3(3, v, f, c));
665
674
  } else {
@@ -716,13 +725,59 @@ flush_callback(libcouchbase_t handle, const void* cookie,
716
725
  }
717
726
  }
718
727
 
728
+ (void)handle;
729
+ }
730
+
731
+ static void
732
+ version_callback(libcouchbase_t handle, const void *cookie,
733
+ const char *authority, libcouchbase_error_t error,
734
+ const char *bytes, libcouchbase_size_t nbytes)
735
+ {
736
+ context_t *ctx = (context_t *)cookie;
737
+ bucket_t *bucket = ctx->bucket;
738
+ VALUE node, v, *rv = ctx->rv, exc, res;
739
+
740
+ node = authority ? rb_str_new2(authority) : Qnil;
741
+ exc = cb_check_error(error, "failed to get version", node);
742
+ if (exc != Qnil) {
743
+ rb_ivar_set(exc, id_iv_operation, sym_flush);
744
+ if (NIL_P(ctx->exception)) {
745
+ ctx->exception = exc;
746
+ }
747
+ }
748
+
749
+ if (authority) {
750
+ v = rb_str_new((const char*)bytes, nbytes);
751
+ if (bucket->async) { /* asynchronous */
752
+ if (ctx->proc != Qnil) {
753
+ res = rb_class_new_instance(0, NULL, cResult);
754
+ rb_ivar_set(res, id_iv_error, exc);
755
+ rb_ivar_set(res, id_iv_operation, sym_version);
756
+ rb_ivar_set(res, id_iv_node, node);
757
+ rb_ivar_set(res, id_iv_value, v);
758
+ cb_proc_call(ctx->proc, 1, res);
759
+ }
760
+ } else { /* synchronous */
761
+ if (NIL_P(exc)) {
762
+ rb_hash_aset(*rv, node, v);
763
+ }
764
+ }
765
+ } else {
766
+ bucket->seqno--;
767
+ if (bucket->seqno == 0) {
768
+ bucket->io->stop_event_loop(bucket->io);
769
+ rb_hash_delete(object_space, ctx->proc|1);
770
+ }
771
+ }
772
+
719
773
  (void)handle;
720
774
  }
721
775
 
722
776
  static void
723
777
  stat_callback(libcouchbase_t handle, const void* cookie,
724
778
  const char* authority, libcouchbase_error_t error, const void* key,
725
- size_t nkey, const void* bytes, size_t nbytes)
779
+ libcouchbase_size_t nkey, const void* bytes,
780
+ libcouchbase_size_t nbytes)
726
781
  {
727
782
  context_t *ctx = (context_t *)cookie;
728
783
  bucket_t *bucket = ctx->bucket;
@@ -751,12 +806,12 @@ stat_callback(libcouchbase_t handle, const void* cookie,
751
806
  }
752
807
  } else { /* synchronous */
753
808
  if (NIL_P(exc)) {
754
- stats = rb_hash_aref(*rv, node);
809
+ stats = rb_hash_aref(*rv, k);
755
810
  if (NIL_P(stats)) {
756
811
  stats = rb_hash_new();
757
- rb_hash_aset(*rv, node, stats);
812
+ rb_hash_aset(*rv, k, stats);
758
813
  }
759
- rb_hash_aset(stats, k, v);
814
+ rb_hash_aset(stats, node, v);
760
815
  }
761
816
  }
762
817
  } else {
@@ -771,7 +826,8 @@ stat_callback(libcouchbase_t handle, const void* cookie,
771
826
 
772
827
  static void
773
828
  touch_callback(libcouchbase_t handle, const void *cookie,
774
- libcouchbase_error_t error, const void *key, size_t nkey)
829
+ libcouchbase_error_t error, const void *key,
830
+ libcouchbase_size_t nkey)
775
831
  {
776
832
  context_t *ctx = (context_t *)cookie;
777
833
  bucket_t *bucket = ctx->bucket;
@@ -800,7 +856,7 @@ touch_callback(libcouchbase_t handle, const void *cookie,
800
856
  } else { /* synchronous */
801
857
  if (NIL_P(exc)) {
802
858
  success = (error == LIBCOUCHBASE_KEY_ENOENT) ? Qfalse : Qtrue;
803
- rb_ary_push(*rv, success);
859
+ rb_hash_aset(*rv, k, success);
804
860
  }
805
861
  }
806
862
  if (bucket->seqno == 0) {
@@ -812,8 +868,9 @@ touch_callback(libcouchbase_t handle, const void *cookie,
812
868
 
813
869
  static void
814
870
  arithmetic_callback(libcouchbase_t handle, const void *cookie,
815
- libcouchbase_error_t error, const void *key, size_t nkey,
816
- uint64_t value, uint64_t cas)
871
+ libcouchbase_error_t error, const void *key,
872
+ libcouchbase_size_t nkey, libcouchbase_uint64_t value,
873
+ libcouchbase_cas_t cas)
817
874
  {
818
875
  context_t *ctx = (context_t *)cookie;
819
876
  bucket_t *bucket = ctx->bucket;
@@ -869,79 +926,6 @@ arithmetic_callback(libcouchbase_t handle, const void *cookie,
869
926
  (void)handle;
870
927
  }
871
928
 
872
- static char *
873
- parse_path_segment(char *source, const char *key, char **result)
874
- {
875
- size_t len;
876
- char *eot;
877
-
878
- if (source == NULL) {
879
- return NULL;
880
- }
881
- eot = strchr(source, '/');
882
- if (eot > source && strncmp(source, key, eot - source) == 0) {
883
- *eot = '\0';
884
- source = eot + 1;
885
- eot = strchr(source, '/');
886
- len = strlen(source);
887
- if (eot > source || len) {
888
- if (eot) {
889
- *eot = '\0';
890
- eot++;
891
- }
892
- *result = strdup(source);
893
- }
894
- }
895
- return eot;
896
- }
897
-
898
- static void
899
- parse_bucket_uri(VALUE uri, bucket_t *bucket)
900
- {
901
- char *src, *ptr, *eot, sep = '\0';
902
-
903
- ptr = src = strdup(StringValueCStr(uri));
904
- eot = strchr(ptr, ':');
905
- if (eot < ptr || strncmp(ptr, "http", eot - ptr) != 0) {
906
- free(src);
907
- rb_raise(rb_eArgError, "invalid URI format: missing schema");
908
- return;
909
- }
910
- ptr = eot + 1;
911
- if (ptr[0] != '/' || ptr[1] != '/') {
912
- free(src);
913
- rb_raise(rb_eArgError, "invalid URI format.");
914
- return;
915
- }
916
- ptr += 2;
917
- eot = ptr;
918
- while (*eot) {
919
- if (*eot == '?' || *eot == '#' || *eot == ':' || *eot == '/') {
920
- break;
921
- }
922
- ++eot;
923
- }
924
- if (eot > ptr) {
925
- sep = *eot;
926
- *eot = '\0';
927
- bucket->hostname = strdup(ptr);
928
- }
929
- ptr = eot + 1;
930
- eot = strchr(ptr, '/');
931
- if (sep == ':') {
932
- if (eot > ptr) {
933
- *eot = '\0';
934
- }
935
- bucket->port = (uint16_t)atoi(ptr);
936
- if (eot > ptr) {
937
- ptr = eot + 1;
938
- }
939
- }
940
- ptr = parse_path_segment(ptr, "pools", &bucket->pool);
941
- parse_path_segment(ptr, "buckets", &bucket->bucket);
942
- free(src);
943
- }
944
-
945
929
  static int
946
930
  cb_first_value_i(VALUE key, VALUE value, VALUE arg)
947
931
  {
@@ -952,28 +936,8 @@ cb_first_value_i(VALUE key, VALUE value, VALUE arg)
952
936
  return ST_STOP;
953
937
  }
954
938
 
955
- static VALUE
956
- cb_bucket_inspect(VALUE self)
957
- {
958
- VALUE str;
959
- bucket_t *bucket = DATA_PTR(self);
960
- char buf[200];
961
-
962
- str = rb_str_buf_new2("#<");
963
- rb_str_buf_cat2(str, rb_obj_classname(self));
964
- snprintf(buf, 25, ":%p \"", (void *)self);
965
- rb_str_buf_cat2(str, buf);
966
- rb_str_append(str, rb_ivar_get(self, id_iv_url));
967
- snprintf(buf, 150, "\" default_format=:%s, default_flags=0x%x, quiet=%s>",
968
- rb_id2name(SYM2ID(bucket->default_format)),
969
- bucket->default_flags,
970
- bucket->quiet ? "true" : "false");
971
- rb_str_buf_cat2(str, buf);
972
-
973
- return str;
974
- }
975
-
976
939
  /*
940
+ * @private
977
941
  * @return [Fixnum] number of scheduled operations
978
942
  */
979
943
  static VALUE
@@ -992,13 +956,13 @@ cb_bucket_free(void *ptr)
992
956
  if (bucket) {
993
957
  if (bucket->handle) {
994
958
  libcouchbase_destroy(bucket->handle);
995
- free(bucket->authority);
996
- free(bucket->hostname);
997
- free(bucket->pool);
998
- free(bucket->bucket);
999
- free(bucket->username);
1000
- free(bucket->password);
1001
959
  }
960
+ free(bucket->authority);
961
+ free(bucket->hostname);
962
+ free(bucket->pool);
963
+ free(bucket->bucket);
964
+ free(bucket->username);
965
+ free(bucket->password);
1002
966
  free(bucket);
1003
967
  }
1004
968
  }
@@ -1014,97 +978,69 @@ cb_bucket_mark(void *ptr)
1014
978
  }
1015
979
  }
1016
980
 
1017
- /*
1018
- * @return [Bucket] new instance (see Bucket#initialize)
1019
- */
1020
- static VALUE
1021
- cb_bucket_new(int argc, VALUE *argv, VALUE klass)
1022
- {
1023
- VALUE obj;
1024
- bucket_t *bucket;
1025
-
1026
- /* allocate new bucket struct and set it to zero */
1027
- obj = Data_Make_Struct(klass, bucket_t, cb_bucket_mark, cb_bucket_free,
1028
- bucket);
1029
- rb_obj_call_init(obj, argc, argv);
1030
- return obj;
1031
- }
1032
-
1033
- /*
1034
- * @overload initialize(url, options = {})
1035
- * Initialize bucket using URI of the cluster and options. It is possible
1036
- * to override some parts of URI using the options keys (e.g. :host or
1037
- * :port)
1038
- *
1039
- * @param [String] url The full URL of management API of the cluster.
1040
- * @param [Hash] options The options for connection. See options definition
1041
- * below.
1042
- *
1043
- * @overload initialize(options = {})
1044
- * Initialize bucket using options only.
1045
- *
1046
- * @param [Hash] options The options for operation for connection
1047
- * @option options [String] :host ("localhost") the hostname or IP address
1048
- * of the node
1049
- * @option options [Fixnum] :port (8091) the port of the managemenent API
1050
- * @option options [String] :pool ("default") the pool name
1051
- * @option options [String] :bucket ("default") the bucket name
1052
- * @option options [Fixnum] :default_ttl (0) the TTL used by default during
1053
- * storing key-value pairs.
1054
- * @option options [Fixnum] :default_flags (0) the default flags.
1055
- * @option options [Symbol] :default_format (:document) the format, which
1056
- * will be used for values by default. Note that changing format will
1057
- * amend flags. (see Bucket#default_format)
1058
- * @option options [String] :username (nil) the user name to connect to the
1059
- * cluster. Used to authenticate on management API.
1060
- * @option options [String] :password (nil) the password of the user.
1061
- * @option options [Boolean] :quiet (true) the flag controlling if raising
1062
- * exception when the client executes operations on unexising keys. If it
1063
- * is +true+ it will raise +Couchbase::NotFoundError+ exceptions. The
1064
- * default behaviour is to return +nil+ value silently (might be useful in
1065
- * Rails cache).
1066
- *
1067
- * @example Initialize connection using default options
1068
- * Couchbase.new
1069
- *
1070
- * @example Select custom bucket
1071
- * Couchbase.new(:bucket => 'foo')
1072
- * Couchbase.new('http://localhost:8091/pools/default/buckets/foo')
1073
- *
1074
- * @example Connect to protected bucket
1075
- * Couchbase.new(:bucket => 'protected', :username => 'protected', :password => 'secret')
1076
- * Couchbase.new('http://localhost:8091/pools/default/buckets/protected',
1077
- * :username => 'protected', :password => 'secret')
1078
- *
1079
- * @return [Bucket]
1080
- */
1081
- static VALUE
1082
- cb_bucket_init(int argc, VALUE *argv, VALUE self)
981
+ static void
982
+ do_scan_connection_options(bucket_t *bucket, int argc, VALUE *argv)
1083
983
  {
1084
- VALUE uri, opts, arg, buf;
1085
- libcouchbase_error_t err;
1086
- bucket_t *bucket = DATA_PTR(self);
984
+ VALUE uri, opts, arg;
1087
985
  size_t len;
1088
986
 
1089
- bucket->exception = Qnil;
1090
- bucket->hostname = strdup("localhost");
1091
- bucket->port = 8091;
1092
- bucket->pool = strdup("default");
1093
- bucket->bucket = strdup("default");
1094
- bucket->async = 0;
1095
- bucket->quiet = 1;
1096
- bucket->default_flags = 0;
1097
- bucket->default_format = sym_document;
1098
- bucket->on_error_proc = Qnil;
1099
-
1100
987
  if (rb_scan_args(argc, argv, "02", &uri, &opts) > 0) {
1101
988
  if (TYPE(uri) == T_HASH && argc == 1) {
1102
989
  opts = uri;
1103
990
  uri = Qnil;
1104
991
  }
1105
992
  if (uri != Qnil) {
993
+ const char path_re[] = "^(/pools/([A-Za-z0-9_.-]+)(/buckets/([A-Za-z0-9_.-]+))?)?";
994
+ VALUE match, uri_obj, re;
995
+
1106
996
  Check_Type(uri, T_STRING);
1107
- parse_bucket_uri(uri, bucket);
997
+ uri_obj = rb_funcall(mURI, id_parse, 1, uri);
998
+
999
+ arg = rb_funcall(uri_obj, id_scheme, 0);
1000
+ if (arg == Qnil || rb_str_cmp(arg, rb_str_new2("http"))) {
1001
+ rb_raise(rb_eArgError, "invalid URI: invalid scheme");
1002
+ }
1003
+
1004
+ arg = rb_funcall(uri_obj, id_user, 0);
1005
+ if (arg != Qnil) {
1006
+ free(bucket->username);
1007
+ bucket->username = strdup(RSTRING_PTR(arg));
1008
+ if (bucket->username == NULL) {
1009
+ rb_raise(eNoMemoryError, "failed to allocate memory for Bucket");
1010
+ }
1011
+ }
1012
+
1013
+ arg = rb_funcall(uri_obj, id_password, 0);
1014
+ if (arg != Qnil) {
1015
+ free(bucket->password);
1016
+ bucket->password = strdup(RSTRING_PTR(arg));
1017
+ if (bucket->password == NULL) {
1018
+ rb_raise(eNoMemoryError, "failed to allocate memory for Bucket");
1019
+ }
1020
+ }
1021
+ arg = rb_funcall(uri_obj, id_host, 0);
1022
+ if (arg != Qnil) {
1023
+ free(bucket->hostname);
1024
+ bucket->hostname = strdup(RSTRING_PTR(arg));
1025
+ if (bucket->hostname == NULL) {
1026
+ rb_raise(eNoMemoryError, "failed to allocate memory for Bucket");
1027
+ }
1028
+ } else {
1029
+ rb_raise(rb_eArgError, "invalid URI: missing hostname");
1030
+ }
1031
+
1032
+ arg = rb_funcall(uri_obj, id_port, 0);
1033
+ bucket->port = NIL_P(arg) ? 8091 : (uint16_t)NUM2UINT(arg);
1034
+
1035
+ arg = rb_funcall(uri_obj, id_path, 0);
1036
+ re = rb_reg_new(path_re, sizeof(path_re) - 1, 0);
1037
+ match = rb_funcall(re, id_match, 1, arg);
1038
+ arg = rb_reg_nth_match(2, match);
1039
+ free(bucket->pool);
1040
+ bucket->pool = strdup(NIL_P(arg) ? "default" : RSTRING_PTR(arg));
1041
+ arg = rb_reg_nth_match(4, match);
1042
+ free(bucket->bucket);
1043
+ bucket->bucket = strdup(NIL_P(arg) ? "default" : RSTRING_PTR(arg));
1108
1044
  }
1109
1045
  if (TYPE(opts) == T_HASH) {
1110
1046
  arg = rb_hash_aref(opts, sym_hostname);
@@ -1130,10 +1066,16 @@ cb_bucket_init(int argc, VALUE *argv, VALUE self)
1130
1066
  }
1131
1067
  arg = rb_hash_aref(opts, sym_username);
1132
1068
  if (arg != Qnil) {
1069
+ if (bucket->username) {
1070
+ free(bucket->username);
1071
+ }
1133
1072
  bucket->username = strdup(StringValueCStr(arg));
1134
1073
  }
1135
1074
  arg = rb_hash_aref(opts, sym_password);
1136
1075
  if (arg != Qnil) {
1076
+ if (bucket->password) {
1077
+ free(bucket->password);
1078
+ }
1137
1079
  bucket->password = strdup(StringValueCStr(arg));
1138
1080
  }
1139
1081
  arg = rb_hash_aref(opts, sym_port);
@@ -1143,6 +1085,14 @@ cb_bucket_init(int argc, VALUE *argv, VALUE self)
1143
1085
  if (RTEST(rb_funcall(opts, id_has_key_p, 1, sym_quiet))) {
1144
1086
  bucket->quiet = RTEST(rb_hash_aref(opts, sym_quiet));
1145
1087
  }
1088
+ arg = rb_hash_aref(opts, sym_timeout);
1089
+ if (arg != Qnil) {
1090
+ bucket->timeout = (uint32_t)NUM2ULONG(arg);
1091
+ }
1092
+ arg = rb_hash_aref(opts, sym_default_ttl);
1093
+ if (arg != Qnil) {
1094
+ bucket->default_ttl = (uint32_t)NUM2ULONG(arg);
1095
+ }
1146
1096
  arg = rb_hash_aref(opts, sym_default_flags);
1147
1097
  if (arg != Qnil) {
1148
1098
  bucket->default_flags = (uint32_t)NUM2ULONG(arg);
@@ -1172,13 +1122,28 @@ cb_bucket_init(int argc, VALUE *argv, VALUE self)
1172
1122
  }
1173
1123
  }
1174
1124
  len = strlen(bucket->hostname) + 10;
1125
+ if (bucket->authority) {
1126
+ free(bucket->authority);
1127
+ }
1175
1128
  bucket->authority = calloc(len, sizeof(char));
1176
1129
  if (bucket->authority == NULL) {
1177
1130
  rb_raise(eNoMemoryError, "failed to allocate memory for Bucket");
1178
1131
  }
1179
1132
  snprintf(bucket->authority, len, "%s:%u", bucket->hostname, bucket->port);
1133
+ }
1134
+
1135
+ static void
1136
+ do_connect(bucket_t *bucket)
1137
+ {
1138
+ libcouchbase_error_t err;
1139
+
1140
+ if (bucket->handle) {
1141
+ libcouchbase_destroy(bucket->handle);
1142
+ bucket->handle = NULL;
1143
+ bucket->io = NULL;
1144
+ }
1180
1145
  bucket->io = libcouchbase_create_io_ops(LIBCOUCHBASE_IO_OPS_DEFAULT, NULL, &err);
1181
- if (bucket->io == NULL) {
1146
+ if (bucket->io == NULL && err != LIBCOUCHBASE_SUCCESS) {
1182
1147
  rb_exc_raise(cb_check_error(err, "failed to create IO instance", Qnil));
1183
1148
  }
1184
1149
  bucket->handle = libcouchbase_create(bucket->authority,
@@ -1195,78 +1160,241 @@ cb_bucket_init(int argc, VALUE *argv, VALUE self)
1195
1160
  (void)libcouchbase_set_stat_callback(bucket->handle, stat_callback);
1196
1161
  (void)libcouchbase_set_flush_callback(bucket->handle, flush_callback);
1197
1162
  (void)libcouchbase_set_arithmetic_callback(bucket->handle, arithmetic_callback);
1163
+ (void)libcouchbase_set_version_callback(bucket->handle, version_callback);
1198
1164
 
1199
1165
  err = libcouchbase_connect(bucket->handle);
1200
1166
  if (err != LIBCOUCHBASE_SUCCESS) {
1167
+ libcouchbase_destroy(bucket->handle);
1168
+ bucket->handle = NULL;
1169
+ bucket->io = NULL;
1201
1170
  rb_exc_raise(cb_check_error(err, "failed to connect libcouchbase instance to server", Qnil));
1202
1171
  }
1172
+ bucket->exception = Qnil;
1203
1173
  libcouchbase_wait(bucket->handle);
1204
1174
  if (bucket->exception != Qnil) {
1175
+ libcouchbase_destroy(bucket->handle);
1176
+ bucket->handle = NULL;
1177
+ bucket->io = NULL;
1205
1178
  rb_exc_raise(bucket->exception);
1206
1179
  }
1207
1180
 
1208
- rb_ivar_set(self, id_iv_authority, rb_str_new2(bucket->authority));
1209
- rb_ivar_set(self, id_iv_bucket, rb_str_new2(bucket->bucket));
1210
- rb_ivar_set(self, id_iv_hostname, rb_str_new2(bucket->hostname));
1211
- rb_ivar_set(self, id_iv_password, bucket->password ? rb_str_new2(bucket->password) : Qnil);
1212
- rb_ivar_set(self, id_iv_pool, rb_str_new2(bucket->pool));
1213
- rb_ivar_set(self, id_iv_port, UINT2NUM(bucket->port));
1214
- rb_ivar_set(self, id_iv_username, bucket->username ? rb_str_new2(bucket->username) : Qnil);
1215
- rb_ivar_set(self, id_iv_quiet, bucket->quiet ? Qtrue : Qfalse);
1216
- rb_ivar_set(self, id_iv_default_flags, ULONG2NUM(bucket->default_flags));
1217
- rb_ivar_set(self, id_iv_default_format, bucket->default_format);
1218
- rb_ivar_set(self, id_iv_on_error, bucket->on_error_proc);
1219
-
1220
- buf = rb_str_buf_new2("http://");
1221
- rb_str_buf_cat2(buf, bucket->authority);
1222
- rb_str_buf_cat2(buf, "/pools/");
1223
- rb_str_buf_cat2(buf, bucket->pool);
1224
- rb_str_buf_cat2(buf, "/buckets/");
1225
- rb_str_buf_cat2(buf, bucket->bucket);
1226
- rb_str_buf_cat2(buf, "/");
1227
- rb_ivar_set(self, id_iv_url, buf);
1228
-
1229
- return self;
1230
- }
1231
-
1232
- static VALUE
1233
- cb_bucket_async_p(VALUE self)
1234
- {
1235
- bucket_t *bucket = DATA_PTR(self);
1236
- return bucket->async ? Qtrue : Qfalse;
1237
- }
1238
-
1239
- static VALUE
1240
- cb_bucket_quiet_set(VALUE self, VALUE val)
1241
- {
1242
- bucket_t *bucket = DATA_PTR(self);
1243
- VALUE new;
1244
-
1245
- bucket->quiet = RTEST(val);
1246
- new = bucket->quiet ? Qtrue : Qfalse;
1247
- rb_ivar_set(self, id_iv_quiet, new);
1248
- return new;
1181
+ if (bucket->timeout > 0) {
1182
+ libcouchbase_set_timeout(bucket->handle, bucket->timeout);
1183
+ } else {
1184
+ bucket->timeout = libcouchbase_get_timeout(bucket->handle);
1185
+ }
1249
1186
  }
1250
1187
 
1188
+ /*
1189
+ * Create and initialize new Bucket.
1190
+ *
1191
+ * @return [Bucket] new instance
1192
+ *
1193
+ * @see Bucket#initialize
1194
+ */
1251
1195
  static VALUE
1252
- cb_bucket_default_flags_set(VALUE self, VALUE val)
1196
+ cb_bucket_new(int argc, VALUE *argv, VALUE klass)
1253
1197
  {
1254
- bucket_t *bucket = DATA_PTR(self);
1198
+ VALUE obj;
1199
+ bucket_t *bucket;
1255
1200
 
1256
- bucket->default_flags = (uint32_t)NUM2ULONG(val);
1257
- bucket->default_format = flags_get_format(bucket->default_flags);
1258
- rb_ivar_set(self, id_iv_default_format, bucket->default_format);
1259
- rb_ivar_set(self, id_iv_default_flags, val);
1260
- return val;
1201
+ /* allocate new bucket struct and set it to zero */
1202
+ obj = Data_Make_Struct(klass, bucket_t, cb_bucket_mark, cb_bucket_free,
1203
+ bucket);
1204
+ rb_obj_call_init(obj, argc, argv);
1205
+ return obj;
1261
1206
  }
1262
1207
 
1263
- static VALUE
1264
- cb_bucket_default_format_set(VALUE self, VALUE val)
1265
- {
1266
- bucket_t *bucket = DATA_PTR(self);
1267
-
1268
- if (TYPE(val) == T_FIXNUM) {
1269
- switch (FIX2INT(val)) {
1208
+ /*
1209
+ * Initialize new Bucket.
1210
+ *
1211
+ * @overload initialize(url, options = {})
1212
+ * Initialize bucket using URI of the cluster and options. It is possible
1213
+ * to override some parts of URI using the options keys (e.g. :host or
1214
+ * :port)
1215
+ *
1216
+ * @param [String] url The full URL of management API of the cluster.
1217
+ * @param [Hash] options The options for connection. See options definition
1218
+ * below.
1219
+ *
1220
+ * @overload initialize(options = {})
1221
+ * Initialize bucket using options only.
1222
+ *
1223
+ * @param [Hash] options The options for operation for connection
1224
+ * @option options [String] :host ("localhost") the hostname or IP address
1225
+ * of the node
1226
+ * @option options [Fixnum] :port (8091) the port of the managemenent API
1227
+ * @option options [String] :pool ("default") the pool name
1228
+ * @option options [String] :bucket ("default") the bucket name
1229
+ * @option options [Fixnum] :default_ttl (0) the TTL used by default during
1230
+ * storing key-value pairs.
1231
+ * @option options [Fixnum] :default_flags (0) the default flags.
1232
+ * @option options [Symbol] :default_format (:document) the format, which
1233
+ * will be used for values by default. Note that changing format will
1234
+ * amend flags. (see {Bucket#default_format})
1235
+ * @option options [String] :username (nil) the user name to connect to the
1236
+ * cluster. Used to authenticate on management API.
1237
+ * @option options [String] :password (nil) the password of the user.
1238
+ * @option options [Boolean] :quiet (true) the flag controlling if raising
1239
+ * exception when the client executes operations on unexising keys. If it
1240
+ * is +true+ it will raise {Couchbase::Error::NotFound} exceptions. The
1241
+ * default behaviour is to return +nil+ value silently (might be useful in
1242
+ * Rails cache).
1243
+ *
1244
+ * @example Initialize connection using default options
1245
+ * Couchbase.new
1246
+ *
1247
+ * @example Select custom bucket
1248
+ * Couchbase.new(:bucket => 'foo')
1249
+ * Couchbase.new('http://localhost:8091/pools/default/buckets/foo')
1250
+ *
1251
+ * @example Connect to protected bucket
1252
+ * Couchbase.new(:bucket => 'protected', :username => 'protected', :password => 'secret')
1253
+ * Couchbase.new('http://localhost:8091/pools/default/buckets/protected',
1254
+ * :username => 'protected', :password => 'secret')
1255
+ *
1256
+ * @return [Bucket]
1257
+ */
1258
+ static VALUE
1259
+ cb_bucket_init(int argc, VALUE *argv, VALUE self)
1260
+ {
1261
+ bucket_t *bucket = DATA_PTR(self);
1262
+
1263
+ bucket->exception = Qnil;
1264
+ bucket->hostname = strdup("localhost");
1265
+ bucket->port = 8091;
1266
+ bucket->pool = strdup("default");
1267
+ bucket->bucket = strdup("default");
1268
+ bucket->async = 0;
1269
+ bucket->quiet = 1;
1270
+ bucket->default_ttl = 0;
1271
+ bucket->default_flags = 0;
1272
+ bucket->default_format = sym_document;
1273
+ bucket->on_error_proc = Qnil;
1274
+ bucket->timeout = 0;
1275
+
1276
+ do_scan_connection_options(bucket, argc, argv);
1277
+ do_connect(bucket);
1278
+
1279
+ return self;
1280
+ }
1281
+
1282
+ /*
1283
+ * Reconnect the bucket
1284
+ *
1285
+ * Reconnect the bucket using initial configuration with optional
1286
+ * redefinition.
1287
+ *
1288
+ * @overload reconnect(url, options = {})
1289
+ * see {Bucket#initialize Bucket#initialize(url, options)}
1290
+ *
1291
+ * @overload reconnect(options = {})
1292
+ * see {Bucket#initialize Bucket#initialize(options)}
1293
+ *
1294
+ * @example reconnect with current parameters
1295
+ * c.reconnect
1296
+ *
1297
+ * @example reconnect the instance to another bucket
1298
+ * c.reconnect(:bucket => 'new')
1299
+ */
1300
+ static VALUE
1301
+ cb_bucket_reconnect(int argc, VALUE *argv, VALUE self)
1302
+ {
1303
+ bucket_t *bucket = DATA_PTR(self);
1304
+
1305
+ do_scan_connection_options(bucket, argc, argv);
1306
+ do_connect(bucket);
1307
+
1308
+ return self;
1309
+ }
1310
+
1311
+ /* Document-method: connected?
1312
+ * Check whether the instance connected to the cluster.
1313
+ *
1314
+ * @return [Boolean] +true+ if the instance connected to the cluster
1315
+ */
1316
+ static VALUE
1317
+ cb_bucket_connected_p(VALUE self)
1318
+ {
1319
+ bucket_t *bucket = DATA_PTR(self);
1320
+ return bucket->handle ? Qtrue : Qfalse;
1321
+ }
1322
+
1323
+ /* Document-method: async?
1324
+ * Check whether the connection asynchronous.
1325
+ *
1326
+ * By default all operations are synchronous and block waiting for
1327
+ * results, but you can make them asynchronous and run event loop
1328
+ * explicitly. (see {Bucket#run})
1329
+ *
1330
+ * @example Return value of #get operation depending on async flag
1331
+ * connection = Connection.new
1332
+ * connection.async? #=> false
1333
+ *
1334
+ * connection.run do |conn|
1335
+ * conn.async? #=> true
1336
+ * end
1337
+ *
1338
+ * @return [Boolean] +true+ if the connection if asynchronous
1339
+ *
1340
+ * @see Bucket#run
1341
+ */
1342
+ static VALUE
1343
+ cb_bucket_async_p(VALUE self)
1344
+ {
1345
+ bucket_t *bucket = DATA_PTR(self);
1346
+ return bucket->async ? Qtrue : Qfalse;
1347
+ }
1348
+
1349
+ static VALUE
1350
+ cb_bucket_quiet_get(VALUE self)
1351
+ {
1352
+ bucket_t *bucket = DATA_PTR(self);
1353
+ return bucket->quiet ? Qtrue : Qfalse;
1354
+ }
1355
+
1356
+ static VALUE
1357
+ cb_bucket_quiet_set(VALUE self, VALUE val)
1358
+ {
1359
+ bucket_t *bucket = DATA_PTR(self);
1360
+ VALUE new;
1361
+
1362
+ bucket->quiet = RTEST(val);
1363
+ new = bucket->quiet ? Qtrue : Qfalse;
1364
+ return new;
1365
+ }
1366
+
1367
+ static VALUE
1368
+ cb_bucket_default_flags_get(VALUE self)
1369
+ {
1370
+ bucket_t *bucket = DATA_PTR(self);
1371
+ return ULONG2NUM(bucket->default_flags);
1372
+ }
1373
+
1374
+ static VALUE
1375
+ cb_bucket_default_flags_set(VALUE self, VALUE val)
1376
+ {
1377
+ bucket_t *bucket = DATA_PTR(self);
1378
+
1379
+ bucket->default_flags = (uint32_t)NUM2ULONG(val);
1380
+ bucket->default_format = flags_get_format(bucket->default_flags);
1381
+ return val;
1382
+ }
1383
+
1384
+ static VALUE
1385
+ cb_bucket_default_format_get(VALUE self)
1386
+ {
1387
+ bucket_t *bucket = DATA_PTR(self);
1388
+ return bucket->default_format;
1389
+ }
1390
+
1391
+ static VALUE
1392
+ cb_bucket_default_format_set(VALUE self, VALUE val)
1393
+ {
1394
+ bucket_t *bucket = DATA_PTR(self);
1395
+
1396
+ if (TYPE(val) == T_FIXNUM) {
1397
+ switch (FIX2INT(val)) {
1270
1398
  case FMT_DOCUMENT:
1271
1399
  val = sym_document;
1272
1400
  break;
@@ -1281,8 +1409,6 @@ cb_bucket_default_format_set(VALUE self, VALUE val)
1281
1409
  if (val == sym_document || val == sym_marshal || val == sym_plain) {
1282
1410
  bucket->default_format = val;
1283
1411
  bucket->default_flags = flags_set_format(bucket->default_flags, val);
1284
- rb_ivar_set(self, id_iv_default_format, val);
1285
- rb_ivar_set(self, id_iv_default_flags, ULONG2NUM(bucket->default_flags));
1286
1412
  }
1287
1413
 
1288
1414
  return val;
@@ -1298,7 +1424,6 @@ cb_bucket_on_error_set(VALUE self, VALUE val)
1298
1424
  } else {
1299
1425
  bucket->on_error_proc = Qnil;
1300
1426
  }
1301
- rb_ivar_set(self, id_iv_on_error, bucket->on_error_proc);
1302
1427
 
1303
1428
  return bucket->on_error_proc;
1304
1429
  }
@@ -1315,6 +1440,212 @@ cb_bucket_on_error_get(VALUE self)
1315
1440
  }
1316
1441
  }
1317
1442
 
1443
+ static VALUE
1444
+ cb_bucket_timeout_get(VALUE self)
1445
+ {
1446
+ bucket_t *bucket = DATA_PTR(self);
1447
+ return ULONG2NUM(bucket->timeout);
1448
+ }
1449
+
1450
+ static VALUE
1451
+ cb_bucket_timeout_set(VALUE self, VALUE val)
1452
+ {
1453
+ bucket_t *bucket = DATA_PTR(self);
1454
+ VALUE tmval;
1455
+
1456
+ bucket->timeout = (uint32_t)NUM2ULONG(val);
1457
+ libcouchbase_set_timeout(bucket->handle, bucket->timeout);
1458
+ tmval = ULONG2NUM(bucket->timeout);
1459
+
1460
+ return tmval;
1461
+ }
1462
+
1463
+ /* Document-method: hostname
1464
+ * @return [String] the host name of the management interface (default: "localhost")
1465
+ */
1466
+ static VALUE
1467
+ cb_bucket_hostname_get(VALUE self)
1468
+ {
1469
+ bucket_t *bucket = DATA_PTR(self);
1470
+ if (bucket->handle) {
1471
+ if (bucket->hostname) {
1472
+ free(bucket->hostname);
1473
+ bucket->hostname = NULL;
1474
+ }
1475
+ bucket->hostname = strdup(libcouchbase_get_host(bucket->handle));
1476
+ if (bucket->hostname == NULL) {
1477
+ rb_raise(eNoMemoryError, "failed to allocate memory for Bucket");
1478
+ }
1479
+ }
1480
+ return rb_str_new2(bucket->hostname);
1481
+ }
1482
+
1483
+ /* Document-method: port
1484
+ * @return [Fixnum] the port number of the management interface (default: 8091)
1485
+ */
1486
+ static VALUE
1487
+ cb_bucket_port_get(VALUE self)
1488
+ {
1489
+ bucket_t *bucket = DATA_PTR(self);
1490
+ if (bucket->handle) {
1491
+ bucket->port = atoi(libcouchbase_get_port(bucket->handle));
1492
+ }
1493
+ return UINT2NUM(bucket->port);
1494
+ }
1495
+
1496
+ /* Document-method: authority
1497
+ * @return [String] host with port
1498
+ */
1499
+ static VALUE
1500
+ cb_bucket_authority_get(VALUE self)
1501
+ {
1502
+ bucket_t *bucket = DATA_PTR(self);
1503
+ size_t len;
1504
+
1505
+ (void)cb_bucket_hostname_get(self);
1506
+ (void)cb_bucket_port_get(self);
1507
+ len = strlen(bucket->hostname) + 10;
1508
+ bucket->authority = calloc(len, sizeof(char));
1509
+ if (bucket->authority == NULL) {
1510
+ rb_raise(eNoMemoryError, "failed to allocate memory for Bucket");
1511
+ }
1512
+ snprintf(bucket->authority, len, "%s:%u", bucket->hostname, bucket->port);
1513
+ return rb_str_new2(bucket->authority);
1514
+ }
1515
+
1516
+ /* Document-method: bucket
1517
+ * @return [String] the bucket name
1518
+ */
1519
+ static VALUE
1520
+ cb_bucket_bucket_get(VALUE self)
1521
+ {
1522
+ bucket_t *bucket = DATA_PTR(self);
1523
+ return rb_str_new2(bucket->bucket);
1524
+ }
1525
+
1526
+ /* Document-method: pool
1527
+ * @return [String] the pool name (usually "default")
1528
+ */
1529
+ static VALUE
1530
+ cb_bucket_pool_get(VALUE self)
1531
+ {
1532
+ bucket_t *bucket = DATA_PTR(self);
1533
+ return rb_str_new2(bucket->pool);
1534
+ }
1535
+
1536
+ /* Document-method: username
1537
+ * @return [String] the username for protected buckets (usually matches
1538
+ * the bucket name)
1539
+ */
1540
+ static VALUE
1541
+ cb_bucket_username_get(VALUE self)
1542
+ {
1543
+ bucket_t *bucket = DATA_PTR(self);
1544
+ return rb_str_new2(bucket->username);
1545
+ }
1546
+
1547
+ /* Document-method: password
1548
+ * @return [String] the password for protected buckets
1549
+ */
1550
+ static VALUE
1551
+ cb_bucket_password_get(VALUE self)
1552
+ {
1553
+ bucket_t *bucket = DATA_PTR(self);
1554
+ return rb_str_new2(bucket->password);
1555
+ }
1556
+
1557
+ /* Document-method: url
1558
+ * @return [String] the address of the cluster management interface
1559
+ */
1560
+ static VALUE
1561
+ cb_bucket_url_get(VALUE self)
1562
+ {
1563
+ bucket_t *bucket = DATA_PTR(self);
1564
+ VALUE str;
1565
+
1566
+ (void)cb_bucket_authority_get(self);
1567
+ str = rb_str_buf_new2("http://");
1568
+ rb_str_buf_cat2(str, bucket->authority);
1569
+ rb_str_buf_cat2(str, "/pools/");
1570
+ rb_str_buf_cat2(str, bucket->pool);
1571
+ rb_str_buf_cat2(str, "/buckets/");
1572
+ rb_str_buf_cat2(str, bucket->bucket);
1573
+ rb_str_buf_cat2(str, "/");
1574
+ return str;
1575
+ }
1576
+
1577
+ /*
1578
+ * Returns a string containing a human-readable representation of the
1579
+ * Bucket.
1580
+ *
1581
+ * @return [String]
1582
+ */
1583
+ static VALUE
1584
+ cb_bucket_inspect(VALUE self)
1585
+ {
1586
+ VALUE str;
1587
+ bucket_t *bucket = DATA_PTR(self);
1588
+ char buf[200];
1589
+
1590
+ str = rb_str_buf_new2("#<");
1591
+ rb_str_buf_cat2(str, rb_obj_classname(self));
1592
+ snprintf(buf, 25, ":%p \"", (void *)self);
1593
+ (void)cb_bucket_authority_get(self);
1594
+ rb_str_buf_cat2(str, buf);
1595
+ rb_str_buf_cat2(str, "http://");
1596
+ rb_str_buf_cat2(str, bucket->authority);
1597
+ rb_str_buf_cat2(str, "/pools/");
1598
+ rb_str_buf_cat2(str, bucket->pool);
1599
+ rb_str_buf_cat2(str, "/buckets/");
1600
+ rb_str_buf_cat2(str, bucket->bucket);
1601
+ rb_str_buf_cat2(str, "/");
1602
+ snprintf(buf, 150, "\" default_format=:%s, default_flags=0x%x, quiet=%s, connected=%s, timeout=%u>",
1603
+ rb_id2name(SYM2ID(bucket->default_format)),
1604
+ bucket->default_flags,
1605
+ bucket->quiet ? "true" : "false",
1606
+ bucket->handle ? "true" : "false",
1607
+ bucket->timeout);
1608
+ rb_str_buf_cat2(str, buf);
1609
+
1610
+ return str;
1611
+ }
1612
+
1613
+ /*
1614
+ * Delete the specified key
1615
+ *
1616
+ * @overload delete(key, options = {})
1617
+ * @param key [String, Symbol] Key used to reference the value.
1618
+ * @param options [Hash] Options for operation.
1619
+ * @option options [Boolean] :quiet (self.quiet) If set to +true+, the
1620
+ * operation won't raise error for missing key, it will return +nil+.
1621
+ * Otherwise it will raise error in synchronous mode. In asynchronous
1622
+ * mode this option ignored.
1623
+ * @option options [Fixnum] :cas The CAS value for an object. This value
1624
+ * created on the server and is guaranteed to be unique for each value of
1625
+ * a given key. This value is used to provide simple optimistic
1626
+ * concurrency control when multiple clients or threads try to
1627
+ * update/delete an item simultaneously.
1628
+ *
1629
+ * @raise [Couchbase::Error::Connect] if connection closed (see {Bucket#reconnect})
1630
+ * @raise [ArgumentError] when passing the block in synchronous mode
1631
+ * @raise [Couchbase::Error::KeyExists] on CAS mismatch
1632
+ * @raise [Couchbase::Error::NotFound] if key is missing in verbose mode
1633
+ *
1634
+ * @example Delete the key in quiet mode (default)
1635
+ * c.set("foo", "bar")
1636
+ * c.delete("foo") #=> true
1637
+ * c.delete("foo") #=> false
1638
+ *
1639
+ * @example Delete the key verbosely
1640
+ * c.set("foo", "bar")
1641
+ * c.delete("foo", :quiet => false) #=> true
1642
+ * c.delete("foo", :quiet => false) #=> will raise Couchbase::Error::NotFound
1643
+ *
1644
+ * @example Delete the key with version check
1645
+ * ver = c.set("foo", "bar") #=> 5992859822302167040
1646
+ * c.delete("foo", :cas => 123456) #=> will raise Couchbase::Error::KeyExists
1647
+ * c.delete("foo", :cas => ver) #=> true
1648
+ */
1318
1649
  static VALUE
1319
1650
  cb_bucket_delete(int argc, VALUE *argv, VALUE self)
1320
1651
  {
@@ -1323,9 +1654,13 @@ cb_bucket_delete(int argc, VALUE *argv, VALUE self)
1323
1654
  VALUE k, c, rv, proc, exc, opts;
1324
1655
  char *key;
1325
1656
  size_t nkey;
1326
- uint64_t cas = 0;
1657
+ libcouchbase_cas_t cas = 0;
1327
1658
  libcouchbase_error_t err;
1659
+ long seqno;
1328
1660
 
1661
+ if (bucket->handle == NULL) {
1662
+ rb_raise(eConnectError, "closed connection");
1663
+ }
1329
1664
  rb_scan_args(argc, argv, "11&", &k, &opts, &proc);
1330
1665
  if (!bucket->async && proc != Qnil) {
1331
1666
  rb_raise(rb_eArgError, "synchronous mode doesn't support callbacks");
@@ -1357,6 +1692,8 @@ cb_bucket_delete(int argc, VALUE *argv, VALUE self)
1357
1692
  ctx->rv = &rv;
1358
1693
  ctx->bucket = bucket;
1359
1694
  ctx->exception = Qnil;
1695
+ seqno = bucket->seqno;
1696
+ bucket->seqno++;
1360
1697
  err = libcouchbase_remove(bucket->handle, (const void *)ctx,
1361
1698
  (const void *)key, nkey, cas);
1362
1699
  exc = cb_check_error(err, "failed to schedule delete request", Qnil);
@@ -1364,11 +1701,13 @@ cb_bucket_delete(int argc, VALUE *argv, VALUE self)
1364
1701
  free(ctx);
1365
1702
  rb_exc_raise(exc);
1366
1703
  }
1367
- bucket->seqno++;
1368
1704
  if (bucket->async) {
1369
1705
  return Qnil;
1370
1706
  } else {
1371
- bucket->io->run_event_loop(bucket->io);
1707
+ if (bucket->seqno - seqno > 0) {
1708
+ /* we have some operations pending */
1709
+ bucket->io->run_event_loop(bucket->io);
1710
+ }
1372
1711
  exc = ctx->exception;
1373
1712
  free(ctx);
1374
1713
  if (exc != Qnil) {
@@ -1388,9 +1727,13 @@ cb_bucket_store(libcouchbase_storage_t cmd, int argc, VALUE *argv, VALUE self)
1388
1727
  size_t nkey, nbytes;
1389
1728
  uint32_t flags;
1390
1729
  time_t exp = 0;
1391
- uint64_t cas = 0;
1730
+ libcouchbase_cas_t cas = 0;
1392
1731
  libcouchbase_error_t err;
1732
+ long seqno;
1393
1733
 
1734
+ if (bucket->handle == NULL) {
1735
+ rb_raise(eConnectError, "closed connection");
1736
+ }
1394
1737
  rb_scan_args(argc, argv, "21&", &k, &v, &opts, &proc);
1395
1738
  if (!bucket->async && proc != Qnil) {
1396
1739
  rb_raise(rb_eArgError, "synchronous mode doesn't support callbacks");
@@ -1435,6 +1778,8 @@ cb_bucket_store(libcouchbase_storage_t cmd, int argc, VALUE *argv, VALUE self)
1435
1778
  ctx->proc = proc;
1436
1779
  rb_hash_aset(object_space, ctx->proc|1, ctx->proc);
1437
1780
  ctx->exception = Qnil;
1781
+ seqno = bucket->seqno;
1782
+ bucket->seqno++;
1438
1783
  err = libcouchbase_store(bucket->handle, (const void *)ctx, cmd,
1439
1784
  (const void *)key, nkey, bytes, nbytes, flags, exp, cas);
1440
1785
  exc = cb_check_error(err, "failed to schedule set request", Qnil);
@@ -1442,11 +1787,13 @@ cb_bucket_store(libcouchbase_storage_t cmd, int argc, VALUE *argv, VALUE self)
1442
1787
  free(ctx);
1443
1788
  rb_exc_raise(exc);
1444
1789
  }
1445
- bucket->seqno++;
1446
1790
  if (bucket->async) {
1447
1791
  return Qnil;
1448
1792
  } else {
1449
- bucket->io->run_event_loop(bucket->io);
1793
+ if (bucket->seqno - seqno > 0) {
1794
+ /* we have some operations pending */
1795
+ bucket->io->run_event_loop(bucket->io);
1796
+ }
1450
1797
  exc = ctx->exception;
1451
1798
  free(ctx);
1452
1799
  if (exc != Qnil) {
@@ -1471,7 +1818,11 @@ cb_bucket_arithmetic(int sign, int argc, VALUE *argv, VALUE self)
1471
1818
  uint64_t delta = 0, initial = 0;
1472
1819
  int create = 0;
1473
1820
  libcouchbase_error_t err;
1821
+ long seqno;
1474
1822
 
1823
+ if (bucket->handle == NULL) {
1824
+ rb_raise(eConnectError, "closed connection");
1825
+ }
1475
1826
  rb_scan_args(argc, argv, "12&", &k, &d, &opts, &proc);
1476
1827
  if (!bucket->async && proc != Qnil) {
1477
1828
  rb_raise(rb_eArgError, "synchronous mode doesn't support callbacks");
@@ -1514,6 +1865,8 @@ cb_bucket_arithmetic(int sign, int argc, VALUE *argv, VALUE self)
1514
1865
  rb_hash_aset(object_space, ctx->proc|1, ctx->proc);
1515
1866
  ctx->exception = Qnil;
1516
1867
  ctx->arithm = sign;
1868
+ seqno = bucket->seqno;
1869
+ bucket->seqno++;
1517
1870
  err = libcouchbase_arithmetic(bucket->handle, (const void *)ctx,
1518
1871
  (const void *)key, nkey, delta, exp, create, initial);
1519
1872
  exc = cb_check_error(err, "failed to schedule arithmetic request", k);
@@ -1521,11 +1874,13 @@ cb_bucket_arithmetic(int sign, int argc, VALUE *argv, VALUE self)
1521
1874
  free(ctx);
1522
1875
  rb_exc_raise(exc);
1523
1876
  }
1524
- bucket->seqno++;
1525
1877
  if (bucket->async) {
1526
1878
  return Qnil;
1527
1879
  } else {
1528
- bucket->io->run_event_loop(bucket->io);
1880
+ if (bucket->seqno - seqno > 0) {
1881
+ /* we have some operations pending */
1882
+ bucket->io->run_event_loop(bucket->io);
1883
+ }
1529
1884
  exc = ctx->exception;
1530
1885
  free(ctx);
1531
1886
  if (exc != Qnil) {
@@ -1535,29 +1890,282 @@ cb_bucket_arithmetic(int sign, int argc, VALUE *argv, VALUE self)
1535
1890
  }
1536
1891
  }
1537
1892
 
1893
+ /*
1894
+ * Increment the value of an existing numeric key
1895
+ *
1896
+ * The increment methods enable you to increase a given stored integer
1897
+ * value. These are the incremental equivalent of the decrement operations
1898
+ * and work on the same basis; updating the value of a key if it can be
1899
+ * parsed to an integer. The update operation occurs on the server and is
1900
+ * provided at the protocol level. This simplifies what would otherwise be a
1901
+ * two-stage get and set operation.
1902
+ *
1903
+ * @note that server values stored and transmitted as unsigned numbers,
1904
+ * therefore if you try to store negative number and then increment or
1905
+ * decrement it will cause overflow. (see "Integer overflow" example
1906
+ * below)
1907
+ *
1908
+ * @overload incr(key, delta = 1, options = {})
1909
+ * @param key [String, Symbol] Key used to reference the value.
1910
+ * @param delta [Fixnum] Integer (up to 64 bits) value to increment
1911
+ * @param options [Hash] Options for operation.
1912
+ * @option options [Boolean] :create (false) If set to +true+, it will
1913
+ * initialize the key with zero value and zero flags (use +:initial+
1914
+ * option to set another initial value). Note: it won't increment the
1915
+ * missing value.
1916
+ * @option options [Fixnum] :initial (0) Integer (up to 64 bits) value for
1917
+ * missing key initialization. This option imply +:create+ option is
1918
+ * +true+.
1919
+ * @option options [Fixnum] :ttl (self.default_ttl) Expiry time for key.
1920
+ * Values larger than 30*24*60*60 seconds (30 days) are interpreted as
1921
+ * absolute times (from the epoch). This option ignored for existent
1922
+ * keys.
1923
+ * @option options [Boolean] :extended (false) If set to +true+, the
1924
+ * operation will return tuple +[value, cas]+, otherwise (by default) it
1925
+ * returns just value.
1926
+ *
1927
+ * @yieldparam ret [Result] the result of operation in asynchronous mode
1928
+ * (valid attributes: +error+, +operation+, +key+, +value+, +cas+).
1929
+ *
1930
+ * @return [Fixnum] the actual value of the key.
1931
+ *
1932
+ * @raise [Couchbase::Error::NotFound] if key is missing and +:create+
1933
+ * option isn't +true+.
1934
+ *
1935
+ * @raise [Couchbase::Error::DeltaBadval] if the key contains non-numeric
1936
+ * value
1937
+ *
1938
+ * @raise [Couchbase::Error::Connect] if connection closed (see {Bucket#reconnect})
1939
+ *
1940
+ * @raise [ArgumentError] when passing the block in synchronous mode
1941
+ *
1942
+ * @example Increment key by one
1943
+ * c.incr("foo")
1944
+ *
1945
+ * @example Increment key by 50
1946
+ * c.incr("foo", 50)
1947
+ *
1948
+ * @example Increment key by one <b>OR</b> initialize with zero
1949
+ * c.incr("foo", :create => true) #=> will return old+1 or 0
1950
+ *
1951
+ * @example Increment key by one <b>OR</b> initialize with three
1952
+ * c.incr("foo", 50, :initial => 3) #=> will return old+50 or 3
1953
+ *
1954
+ * @example Increment key and get its CAS value
1955
+ * val, cas = c.incr("foo", :extended => true)
1956
+ *
1957
+ * @example Integer overflow
1958
+ * c.set("foo", -100)
1959
+ * c.get("foo") #=> -100
1960
+ * c.incr("foo") #=> 18446744073709551517
1961
+ *
1962
+ * @example Asynchronous invocation
1963
+ * c.run do
1964
+ * c.incr("foo") do |ret|
1965
+ * ret.operation #=> :increment
1966
+ * ret.success? #=> true
1967
+ * ret.key #=> "foo"
1968
+ * ret.value
1969
+ * ret.cas
1970
+ * end
1971
+ * end
1972
+ *
1973
+ */
1538
1974
  static VALUE
1539
1975
  cb_bucket_incr(int argc, VALUE *argv, VALUE self)
1540
1976
  {
1541
1977
  return cb_bucket_arithmetic(+1, argc, argv, self);
1542
1978
  }
1543
1979
 
1980
+ /*
1981
+ * Decrement the value of an existing numeric key
1982
+ *
1983
+ * The decrement methods reduce the value of a given key if the
1984
+ * corresponding value can be parsed to an integer value. These operations
1985
+ * are provided at a protocol level to eliminate the need to get, update,
1986
+ * and reset a simple integer value in the database. It supports the use of
1987
+ * an explicit offset value that will be used to reduce the stored value in
1988
+ * the database.
1989
+ *
1990
+ * @note that server values stored and transmitted as unsigned numbers,
1991
+ * therefore if you try to decrement negative or zero key, you will always
1992
+ * get zero.
1993
+ *
1994
+ * @overload decr(key, delta = 1, options = {})
1995
+ * @param key [String, Symbol] Key used to reference the value.
1996
+ * @param delta [Fixnum] Integer (up to 64 bits) value to decrement
1997
+ * @param options [Hash] Options for operation.
1998
+ * @option options [Boolean] :create (false) If set to +true+, it will
1999
+ * initialize the key with zero value and zero flags (use +:initial+
2000
+ * option to set another initial value). Note: it won't decrement the
2001
+ * missing value.
2002
+ * @option options [Fixnum] :initial (0) Integer (up to 64 bits) value for
2003
+ * missing key initialization. This option imply +:create+ option is
2004
+ * +true+.
2005
+ * @option options [Fixnum] :ttl (self.default_ttl) Expiry time for key.
2006
+ * Values larger than 30*24*60*60 seconds (30 days) are interpreted as
2007
+ * absolute times (from the epoch). This option ignored for existent
2008
+ * keys.
2009
+ * @option options [Boolean] :extended (false) If set to +true+, the
2010
+ * operation will return tuple +[value, cas]+, otherwise (by default) it
2011
+ * returns just value.
2012
+ *
2013
+ * @yieldparam ret [Result] the result of operation in asynchronous mode
2014
+ * (valid attributes: +error+, +operation+, +key+, +value+, +cas+).
2015
+ *
2016
+ * @return [Fixnum] the actual value of the key.
2017
+ *
2018
+ * @raise [Couchbase::Error::NotFound] if key is missing and +:create+
2019
+ * option isn't +true+.
2020
+ *
2021
+ * @raise [Couchbase::Error::DeltaBadval] if the key contains non-numeric
2022
+ * value
2023
+ *
2024
+ * @raise [Couchbase::Error::Connect] if connection closed (see {Bucket#reconnect})
2025
+ *
2026
+ * @raise [ArgumentError] when passing the block in synchronous mode
2027
+ *
2028
+ * @example Decrement key by one
2029
+ * c.decr("foo")
2030
+ *
2031
+ * @example Decrement key by 50
2032
+ * c.decr("foo", 50)
2033
+ *
2034
+ * @example Decrement key by one <b>OR</b> initialize with zero
2035
+ * c.decr("foo", :create => true) #=> will return old-1 or 0
2036
+ *
2037
+ * @example Decrement key by one <b>OR</b> initialize with three
2038
+ * c.decr("foo", 50, :initial => 3) #=> will return old-50 or 3
2039
+ *
2040
+ * @example Decrement key and get its CAS value
2041
+ * val, cas = c.decr("foo", :extended => true)
2042
+ *
2043
+ * @example Decrementing zero
2044
+ * c.set("foo", 0)
2045
+ * c.decrement("foo", 100500) #=> 0
2046
+ *
2047
+ * @example Decrementing negative value
2048
+ * c.set("foo", -100)
2049
+ * c.decrement("foo", 100500) #=> 0
2050
+ *
2051
+ * @example Asynchronous invocation
2052
+ * c.run do
2053
+ * c.decr("foo") do |ret|
2054
+ * ret.operation #=> :decrement
2055
+ * ret.success? #=> true
2056
+ * ret.key #=> "foo"
2057
+ * ret.value
2058
+ * ret.cas
2059
+ * end
2060
+ * end
2061
+ *
2062
+ */
1544
2063
  static VALUE
1545
2064
  cb_bucket_decr(int argc, VALUE *argv, VALUE self)
1546
2065
  {
1547
2066
  return cb_bucket_arithmetic(-1, argc, argv, self);
1548
2067
  }
1549
2068
 
2069
+ /*
2070
+ * Obtain an object stored in Couchbase by given key.
2071
+ *
2072
+ * @overload get(*keys, options = {})
2073
+ * @param keys [String, Symbol, Array] One or several keys to fetch
2074
+ * @param options [Hash] Options for operation.
2075
+ * @option options [Boolean] :extended (false) If set to +true+, the
2076
+ * operation will return tuple +[value, flags, cas]+, otherwise (by
2077
+ * default) it returns just value.
2078
+ * @option options [Fixnum] :ttl (self.default_ttl) Expiry time for key.
2079
+ * Values larger than 30*24*60*60 seconds (30 days) are interpreted as
2080
+ * absolute times (from the epoch).
2081
+ * @option options [Boolean] :quiet (self.quiet) If set to +true+, the
2082
+ * operation won't raise error for missing key, it will return +nil+.
2083
+ * Otherwise it will raise error in synchronous mode. In asynchronous
2084
+ * mode this option ignored.
2085
+ *
2086
+ * @yieldparam ret [Result] the result of operation in asynchronous mode
2087
+ * (valid attributes: +error+, +operation+, +key+, +value+, +flags+,
2088
+ * +cas+).
2089
+ *
2090
+ * @return [Object, Array, Hash] the value(s) (or tuples in extended mode)
2091
+ * assiciated with the key.
2092
+ *
2093
+ * @raise [Couchbase::Error::NotFound] if the key is missing in the
2094
+ * bucket.
2095
+ *
2096
+ * @raise [Couchbase::Error::Connect] if connection closed (see {Bucket#reconnect})
2097
+ *
2098
+ * @raise [ArgumentError] when passing the block in synchronous mode
2099
+ *
2100
+ * @example Get single value in quite mode (the default)
2101
+ * c.get("foo") #=> the associated value or nil
2102
+ *
2103
+ * @example Use alternative hash-like syntax
2104
+ * c["foo"] #=> the associated value or nil
2105
+ *
2106
+ * @example Get single value in verbose mode
2107
+ * c.get("missing-foo", :quiet => false) #=> raises Couchbase::NotFound
2108
+ *
2109
+ * @example Get and touch single value. The key won't be accessible after 10 seconds
2110
+ * c.get("foo", :ttl => 10)
2111
+ *
2112
+ * @example Extended get
2113
+ * val, flags, cas = c.get("foo", :extended => true)
2114
+ *
2115
+ * @example Get multiple keys
2116
+ * c.get("foo", "bar", "baz") #=> [val1, val2, val3]
2117
+ *
2118
+ * @example Extended get multiple keys
2119
+ * c.get("foo", "bar", :extended => true)
2120
+ * #=> {"foo" => [val1, flags1, cas1], "bar" => [val2, flags2, cas2]}
2121
+ *
2122
+ * @example Asynchronous get
2123
+ * c.run do
2124
+ * c.get("foo", "bar", "baz") do |res|
2125
+ * ret.operation #=> :get
2126
+ * ret.success? #=> true
2127
+ * ret.key #=> "foo", "bar" or "baz" in separate calls
2128
+ * ret.value
2129
+ * ret.flags
2130
+ * ret.cas
2131
+ * end
2132
+ * end
2133
+ *
2134
+ * @overload get(keys, options = {})
2135
+ * When the method receive hash map, it will behave like it receive list
2136
+ * of keys (+keys.keys+), but also touch each key setting expiry time to
2137
+ * the corresponding value. But unlike usual get this command always
2138
+ * return hash map +{key => value}+ or +{key => [value, flags, cas]}+.
2139
+ *
2140
+ * @param keys [Hash] Map key-ttl
2141
+ * @param options [Hash] Options for operation. (see options definition
2142
+ * above)
2143
+ *
2144
+ * @return [Hash] the values (or tuples in extended mode) assiciated with
2145
+ * the keys.
2146
+ *
2147
+ * @example Get and touch multiple keys
2148
+ * c.get("foo" => 10, "bar" => 20) #=> {"foo" => val1, "bar" => val2}
2149
+ *
2150
+ * @example Extended get and touch multiple keys
2151
+ * c.get({"foo" => 10, "bar" => 20}, :extended => true)
2152
+ * #=> {"foo" => [val1, flags1, cas1], "bar" => [val2, flags2, cas2]}
2153
+ */
1550
2154
  static VALUE
1551
2155
  cb_bucket_get(int argc, VALUE *argv, VALUE self)
1552
2156
  {
1553
2157
  bucket_t *bucket = DATA_PTR(self);
1554
2158
  context_t *ctx;
1555
- VALUE args, rv, proc, exc, vv = Qnil, keys;
2159
+ VALUE args, rv, proc, exc, keys;
1556
2160
  long nn;
1557
2161
  libcouchbase_error_t err;
1558
2162
  struct key_traits *traits;
1559
- int extended;
2163
+ int extended, mgat;
2164
+ long seqno;
1560
2165
 
2166
+ if (bucket->handle == NULL) {
2167
+ rb_raise(eConnectError, "closed connection");
2168
+ }
1561
2169
  rb_scan_args(argc, argv, "0*&", &args, &proc);
1562
2170
  if (!bucket->async && proc != Qnil) {
1563
2171
  rb_raise(rb_eArgError, "synchronous mode doesn't support callbacks");
@@ -1569,6 +2177,7 @@ cb_bucket_get(int argc, VALUE *argv, VALUE self)
1569
2177
  if (ctx == NULL) {
1570
2178
  rb_raise(eNoMemoryError, "failed to allocate memory for context");
1571
2179
  }
2180
+ mgat = traits->mgat;
1572
2181
  keys = traits->keys_ary;
1573
2182
  ctx->proc = proc;
1574
2183
  rb_hash_aset(object_space, ctx->proc|1, ctx->proc);
@@ -1578,9 +2187,8 @@ cb_bucket_get(int argc, VALUE *argv, VALUE self)
1578
2187
  rv = rb_hash_new();
1579
2188
  ctx->rv = &rv;
1580
2189
  ctx->exception = Qnil;
1581
- if (!bucket->async) {
1582
- bucket->seqno = 0;
1583
- }
2190
+ seqno = bucket->seqno;
2191
+ bucket->seqno += nn;
1584
2192
  err = libcouchbase_mget(bucket->handle, (const void *)ctx,
1585
2193
  traits->nkeys, (const void * const *)traits->keys,
1586
2194
  traits->lens, (traits->explicit_ttl) ? traits->ttls : NULL);
@@ -1593,11 +2201,13 @@ cb_bucket_get(int argc, VALUE *argv, VALUE self)
1593
2201
  free(ctx);
1594
2202
  rb_exc_raise(exc);
1595
2203
  }
1596
- bucket->seqno += nn;
1597
2204
  if (bucket->async) {
1598
2205
  return Qnil;
1599
2206
  } else {
1600
- bucket->io->run_event_loop(bucket->io);
2207
+ if (bucket->seqno - seqno > 0) {
2208
+ /* we have some operations pending */
2209
+ bucket->io->run_event_loop(bucket->io);
2210
+ }
1601
2211
  exc = ctx->exception;
1602
2212
  extended = ctx->extended;
1603
2213
  free(ctx);
@@ -1607,27 +2217,88 @@ cb_bucket_get(int argc, VALUE *argv, VALUE self)
1607
2217
  if (bucket->exception != Qnil) {
1608
2218
  rb_exc_raise(bucket->exception);
1609
2219
  }
2220
+ if (mgat || (extended && nn > 1)) {
2221
+ return rv; /* return as a hash {key => [value, flags, cas], ...} */
2222
+ }
1610
2223
  if (nn > 1) {
1611
- if (extended) {
1612
- return rv; /* return as a hash {key => [value, flags, cas], ...} */
1613
- } else {
1614
- long ii;
1615
- VALUE *keys_ptr, ret;
1616
- ret = rb_ary_new();
1617
- keys_ptr = RARRAY_PTR(keys);
1618
- for (ii = 0; ii < nn; ii++) {
1619
- rb_ary_push(ret, rb_hash_aref(rv, keys_ptr[ii]));
1620
- }
1621
- return ret; /* return as an array [value1, value2, ...] */
2224
+ long ii;
2225
+ VALUE *keys_ptr, ret;
2226
+ ret = rb_ary_new();
2227
+ keys_ptr = RARRAY_PTR(keys);
2228
+ for (ii = 0; ii < nn; ii++) {
2229
+ rb_ary_push(ret, rb_hash_aref(rv, keys_ptr[ii]));
1622
2230
  }
2231
+ return ret; /* return as an array [value1, value2, ...] */
1623
2232
  } else {
2233
+ VALUE vv = Qnil;
1624
2234
  rb_hash_foreach(rv, cb_first_value_i, (VALUE)&vv);
1625
2235
  return vv;
1626
2236
  }
1627
2237
  }
1628
2238
  }
1629
2239
 
1630
- static VALUE
2240
+ /*
2241
+ * Update the expiry time of an item
2242
+ *
2243
+ * The +touch+ method allow you to update the expiration time on a given
2244
+ * key. This can be useful for situations where you want to prevent an item
2245
+ * from expiring without resetting the associated value. For example, for a
2246
+ * session database you might want to keep the session alive in the database
2247
+ * each time the user accesses a web page without explicitly updating the
2248
+ * session value, keeping the user's session active and available.
2249
+ *
2250
+ * @overload touch(key, options = {})
2251
+ * @param key [String, Symbol] Key used to reference the value.
2252
+ * @param options [Hash] Options for operation.
2253
+ * @option options [Fixnum] :ttl (self.default_ttl) Expiry time for key.
2254
+ * Values larger than 30*24*60*60 seconds (30 days) are interpreted as
2255
+ * absolute times (from the epoch).
2256
+ *
2257
+ * @yieldparam ret [Result] the result of operation in asynchronous mode
2258
+ * (valid attributes: +error+, +operation+, +key+).
2259
+ *
2260
+ * @return [Boolean] +true+ if the operation was successful and +false+
2261
+ * otherwise.
2262
+ *
2263
+ * @raise [Couchbase::Error::Connect] if connection closed (see {Bucket#reconnect})
2264
+ *
2265
+ * @raise [ArgumentError] when passing the block in synchronous mode
2266
+ *
2267
+ * @example Touch value using +default_ttl+
2268
+ * c.touch("foo")
2269
+ *
2270
+ * @example Touch value using custom TTL (10 seconds)
2271
+ * c.touch("foo", :ttl => 10)
2272
+ *
2273
+ * @overload touch(keys)
2274
+ * @param keys [Hash] The Hash where keys represent the keys in the
2275
+ * database, values -- the expiry times for corresponding key. See
2276
+ * description of +:ttl+ argument above for more information about TTL
2277
+ * values.
2278
+ *
2279
+ * @yieldparam ret [Result] the result of operation for each key in
2280
+ * asynchronous mode (valid attributes: +error+, +operation+, +key+).
2281
+ *
2282
+ * @return [Hash] Mapping keys to result of touch operation (+true+ if the
2283
+ * operation was successful and +false+ otherwise)
2284
+ *
2285
+ * @example Touch several values
2286
+ * c.touch("foo" => 10, :bar => 20) #=> {"foo" => true, "bar" => true}
2287
+ *
2288
+ * @example Touch several values in async mode
2289
+ * c.run do
2290
+ * c.touch("foo" => 10, :bar => 20) do |ret|
2291
+ * ret.operation #=> :touch
2292
+ * ret.success? #=> true
2293
+ * ret.key #=> "foo" and "bar" in separate calls
2294
+ * end
2295
+ * end
2296
+ *
2297
+ * @example Touch single value
2298
+ * c.touch("foo" => 10) #=> true
2299
+ *
2300
+ */
2301
+ static VALUE
1631
2302
  cb_bucket_touch(int argc, VALUE *argv, VALUE self)
1632
2303
  {
1633
2304
  bucket_t *bucket = DATA_PTR(self);
@@ -1636,7 +2307,11 @@ cb_bucket_touch(int argc, VALUE *argv, VALUE self)
1636
2307
  size_t nn;
1637
2308
  libcouchbase_error_t err;
1638
2309
  struct key_traits *traits;
2310
+ long seqno;
1639
2311
 
2312
+ if (bucket->handle == NULL) {
2313
+ rb_raise(eConnectError, "closed connection");
2314
+ }
1640
2315
  rb_scan_args(argc, argv, "0*&", &args, &proc);
1641
2316
  if (!bucket->async && proc != Qnil) {
1642
2317
  rb_raise(rb_eArgError, "synchronous mode doesn't support callbacks");
@@ -1651,26 +2326,29 @@ cb_bucket_touch(int argc, VALUE *argv, VALUE self)
1651
2326
  ctx->proc = proc;
1652
2327
  rb_hash_aset(object_space, ctx->proc|1, ctx->proc);
1653
2328
  ctx->bucket = bucket;
1654
- rv = rb_ary_new();
2329
+ rv = rb_hash_new();
1655
2330
  ctx->rv = &rv;
1656
2331
  ctx->exception = Qnil;
1657
- if (!bucket->async) {
1658
- bucket->seqno = 0;
1659
- }
2332
+ seqno = bucket->seqno;
2333
+ bucket->seqno += nn;
1660
2334
  err = libcouchbase_mtouch(bucket->handle, (const void *)ctx,
1661
2335
  traits->nkeys, (const void * const *)traits->keys,
1662
2336
  traits->lens, traits->ttls);
2337
+ free(traits->keys);
2338
+ free(traits->lens);
1663
2339
  free(traits);
1664
2340
  exc = cb_check_error(err, "failed to schedule touch request", Qnil);
1665
2341
  if (exc != Qnil) {
1666
2342
  free(ctx);
1667
2343
  rb_exc_raise(exc);
1668
2344
  }
1669
- bucket->seqno += nn;
1670
2345
  if (bucket->async) {
1671
2346
  return Qnil;
1672
2347
  } else {
1673
- bucket->io->run_event_loop(bucket->io);
2348
+ if (bucket->seqno - seqno > 0) {
2349
+ /* we have some operations pending */
2350
+ bucket->io->run_event_loop(bucket->io);
2351
+ }
1674
2352
  exc = ctx->exception;
1675
2353
  free(ctx);
1676
2354
  if (exc != Qnil) {
@@ -1680,13 +2358,39 @@ cb_bucket_touch(int argc, VALUE *argv, VALUE self)
1680
2358
  rb_exc_raise(bucket->exception);
1681
2359
  }
1682
2360
  if (nn > 1) {
1683
- return rv;
2361
+ return rv; /* return as a hash {key => true, ...} */
1684
2362
  } else {
1685
- return rb_ary_pop(rv);
2363
+ VALUE vv = Qnil;
2364
+ rb_hash_foreach(rv, cb_first_value_i, (VALUE)&vv);
2365
+ return vv;
1686
2366
  }
1687
2367
  }
1688
2368
  }
1689
2369
 
2370
+ /*
2371
+ * Deletes all values from a server
2372
+ *
2373
+ * @overload flush
2374
+ * @yieldparam [Result] ret the object with +error+, +node+ and +operation+
2375
+ * attributes.
2376
+ *
2377
+ * @return [Boolean] +true+ on success
2378
+ *
2379
+ * @raise [Couchbase::Error::Connect] if connection closed (see {Bucket#reconnect})
2380
+ * @raise [ArgumentError] when passing the block in synchronous mode
2381
+ *
2382
+ * @example Simple flush the bucket
2383
+ * c.flush #=> true
2384
+ *
2385
+ * @example Asynchronous flush
2386
+ * c.run do
2387
+ * c.flush do |ret|
2388
+ * ret.operation #=> :flush
2389
+ * ret.success? #=> true
2390
+ * ret.node #=> "localhost:11211"
2391
+ * end
2392
+ * end
2393
+ */
1690
2394
  static VALUE
1691
2395
  cb_bucket_flush(VALUE self)
1692
2396
  {
@@ -1694,7 +2398,11 @@ cb_bucket_flush(VALUE self)
1694
2398
  context_t *ctx;
1695
2399
  VALUE rv, exc;
1696
2400
  libcouchbase_error_t err;
2401
+ long seqno;
1697
2402
 
2403
+ if (bucket->handle == NULL) {
2404
+ rb_raise(eConnectError, "closed connection");
2405
+ }
1698
2406
  if (!bucket->async && rb_block_given_p()) {
1699
2407
  rb_raise(rb_eArgError, "synchronous mode doesn't support callbacks");
1700
2408
  }
@@ -1712,17 +2420,99 @@ cb_bucket_flush(VALUE self)
1712
2420
  ctx->proc = Qnil;
1713
2421
  }
1714
2422
  rb_hash_aset(object_space, ctx->proc|1, ctx->proc);
2423
+ seqno = bucket->seqno;
2424
+ bucket->seqno++;
1715
2425
  err = libcouchbase_flush(bucket->handle, (const void *)ctx);
1716
2426
  exc = cb_check_error(err, "failed to schedule flush request", Qnil);
1717
2427
  if (exc != Qnil) {
1718
2428
  free(ctx);
1719
2429
  rb_exc_raise(exc);
1720
2430
  }
2431
+ if (bucket->async) {
2432
+ return Qnil;
2433
+ } else {
2434
+ if (bucket->seqno - seqno > 0) {
2435
+ /* we have some operations pending */
2436
+ bucket->io->run_event_loop(bucket->io);
2437
+ }
2438
+ exc = ctx->exception;
2439
+ free(ctx);
2440
+ if (exc != Qnil) {
2441
+ rb_exc_raise(exc);
2442
+ }
2443
+ return rv;
2444
+ }
2445
+ }
2446
+
2447
+ /*
2448
+ * Returns versions of the server for each node in the cluster
2449
+ *
2450
+ * @overload version
2451
+ * @yieldparam [Result] ret the object with +error+, +node+, +operation+
2452
+ * and +value+ attributes.
2453
+ *
2454
+ * @return [Hash] node-version pairs
2455
+ *
2456
+ * @raise [Couchbase::Error::Connect] if connection closed (see {Bucket#reconnect})
2457
+ * @raise [ArgumentError] when passing the block in synchronous mode
2458
+ *
2459
+ * @example Synchronous version request
2460
+ * c.version #=> will render version
2461
+ *
2462
+ * @example Asynchronous version request
2463
+ * c.run do
2464
+ * c.version do |ret|
2465
+ * ret.operation #=> :version
2466
+ * ret.success? #=> true
2467
+ * ret.node #=> "localhost:11211"
2468
+ * ret.value #=> will render version
2469
+ * end
2470
+ * end
2471
+ */
2472
+ static VALUE
2473
+ cb_bucket_version(VALUE self)
2474
+ {
2475
+ bucket_t *bucket = DATA_PTR(self);
2476
+ context_t *ctx;
2477
+ VALUE rv, exc;
2478
+ libcouchbase_error_t err;
2479
+ long seqno;
2480
+
2481
+ if (bucket->handle == NULL) {
2482
+ rb_raise(eConnectError, "closed connection");
2483
+ }
2484
+ if (!bucket->async && rb_block_given_p()) {
2485
+ rb_raise(rb_eArgError, "synchronous mode doesn't support callbacks");
2486
+ }
2487
+ ctx = calloc(1, sizeof(context_t));
2488
+ if (ctx == NULL) {
2489
+ rb_raise(eNoMemoryError, "failed to allocate memory for context");
2490
+ }
2491
+ rv = rb_hash_new();
2492
+ ctx->rv = &rv;
2493
+ ctx->bucket = bucket;
2494
+ ctx->exception = Qnil;
2495
+ if (rb_block_given_p()) {
2496
+ ctx->proc = rb_block_proc();
2497
+ } else {
2498
+ ctx->proc = Qnil;
2499
+ }
2500
+ rb_hash_aset(object_space, ctx->proc|1, ctx->proc);
2501
+ seqno = bucket->seqno;
1721
2502
  bucket->seqno++;
2503
+ err = libcouchbase_server_versions(bucket->handle, (const void *)ctx);
2504
+ exc = cb_check_error(err, "failed to schedule version request", Qnil);
2505
+ if (exc != Qnil) {
2506
+ free(ctx);
2507
+ rb_exc_raise(exc);
2508
+ }
1722
2509
  if (bucket->async) {
1723
2510
  return Qnil;
1724
2511
  } else {
1725
- bucket->io->run_event_loop(bucket->io);
2512
+ if (bucket->seqno - seqno > 0) {
2513
+ /* we have some operations pending */
2514
+ bucket->io->run_event_loop(bucket->io);
2515
+ }
1726
2516
  exc = ctx->exception;
1727
2517
  free(ctx);
1728
2518
  if (exc != Qnil) {
@@ -1732,6 +2522,46 @@ cb_bucket_flush(VALUE self)
1732
2522
  }
1733
2523
  }
1734
2524
 
2525
+ /*
2526
+ * Request server statistics.
2527
+ *
2528
+ * Fetches stats from each node in cluster. Without a key specified the
2529
+ * server will respond with a "default" set of statistical information. In
2530
+ * asynchronous mode each statistic is returned in separate call where the
2531
+ * Result object yielded (+#key+ contains the name of the statistical item
2532
+ * and the +#value+ contains the value, the +#node+ will indicate the server
2533
+ * address). In synchronous mode it returns the hash of stats keys and
2534
+ * node-value pairs as a value.
2535
+ *
2536
+ * @overload stats(arg = nil)
2537
+ * @param [String] arg argument to STATS query
2538
+ * @yieldparam [Result] ret the object with +node+, +key+ and +value+
2539
+ * attributes.
2540
+ *
2541
+ * @example Found how many items in the bucket
2542
+ * total = 0
2543
+ * c.stats["total_items"].each do |key, value|
2544
+ * total += value.to_i
2545
+ * end
2546
+ *
2547
+ * @example Found total items number asynchronously
2548
+ * total = 0
2549
+ * c.run do
2550
+ * c.stats do |ret|
2551
+ * if ret.key == "total_items"
2552
+ * total += ret.value.to_i
2553
+ * end
2554
+ * end
2555
+ * end
2556
+ *
2557
+ * @example Get memory stats (works on couchbase buckets)
2558
+ * c.stats(:memory) #=> {"mem_used"=>{...}, ...}
2559
+ *
2560
+ * @return [Hash] where keys are stat keys, values are host-value pairs
2561
+ *
2562
+ * @raise [Couchbase::Error::Connect] if connection closed (see {Bucket#reconnect})
2563
+ * @raise [ArgumentError] when passing the block in synchronous mode
2564
+ */
1735
2565
  static VALUE
1736
2566
  cb_bucket_stats(int argc, VALUE *argv, VALUE self)
1737
2567
  {
@@ -1741,7 +2571,11 @@ cb_bucket_stats(int argc, VALUE *argv, VALUE self)
1741
2571
  char *key;
1742
2572
  size_t nkey;
1743
2573
  libcouchbase_error_t err;
2574
+ long seqno;
1744
2575
 
2576
+ if (bucket->handle == NULL) {
2577
+ rb_raise(eConnectError, "closed connection");
2578
+ }
1745
2579
  rb_scan_args(argc, argv, "01&", &arg, &proc);
1746
2580
  if (!bucket->async && proc != Qnil) {
1747
2581
  rb_raise(rb_eArgError, "synchronous mode doesn't support callbacks");
@@ -1758,12 +2592,15 @@ cb_bucket_stats(int argc, VALUE *argv, VALUE self)
1758
2592
  rb_hash_aset(object_space, ctx->proc|1, ctx->proc);
1759
2593
  ctx->exception = Qnil;
1760
2594
  if (arg != Qnil) {
2595
+ arg = unify_key(arg);
1761
2596
  key = RSTRING_PTR(arg);
1762
2597
  nkey = RSTRING_LEN(arg);
1763
2598
  } else {
1764
2599
  key = NULL;
1765
2600
  nkey = 0;
1766
2601
  }
2602
+ seqno = bucket->seqno;
2603
+ bucket->seqno++;
1767
2604
  err = libcouchbase_server_stats(bucket->handle, (const void *)ctx,
1768
2605
  key, nkey);
1769
2606
  exc = cb_check_error(err, "failed to schedule stat request", Qnil);
@@ -1771,11 +2608,13 @@ cb_bucket_stats(int argc, VALUE *argv, VALUE self)
1771
2608
  free(ctx);
1772
2609
  rb_exc_raise(exc);
1773
2610
  }
1774
- bucket->seqno++;
1775
2611
  if (bucket->async) {
1776
2612
  return Qnil;
1777
2613
  } else {
1778
- bucket->io->run_event_loop(bucket->io);
2614
+ if (bucket->seqno - seqno > 0) {
2615
+ /* we have some operations pending */
2616
+ bucket->io->run_event_loop(bucket->io);
2617
+ }
1779
2618
  exc = ctx->exception;
1780
2619
  free(ctx);
1781
2620
  if (exc != Qnil) {
@@ -1795,12 +2634,31 @@ do_run(VALUE *args)
1795
2634
  {
1796
2635
  VALUE self = args[0], proc = args[1], exc;
1797
2636
  bucket_t *bucket = DATA_PTR(self);
2637
+ time_t tm;
2638
+ uint32_t old_tmo, new_tmo, diff;
1798
2639
 
2640
+ if (bucket->handle == NULL) {
2641
+ rb_raise(eConnectError, "closed connection");
2642
+ }
2643
+ if (bucket->async) {
2644
+ rb_raise(eInvalidError, "nested #run");
2645
+ }
1799
2646
  bucket->seqno = 0;
1800
2647
  bucket->async = 1;
2648
+
2649
+ tm = time(NULL);
1801
2650
  cb_proc_call(proc, 1, self);
1802
2651
  if (bucket->seqno > 0) {
2652
+ old_tmo = libcouchbase_get_timeout(bucket->handle);
2653
+ diff = (uint32_t)(time(NULL) - tm + 1);
2654
+ diff *= 1000000;
2655
+ new_tmo = bucket->timeout += diff;
2656
+ libcouchbase_set_timeout(bucket->handle, bucket->timeout);
1803
2657
  bucket->io->run_event_loop(bucket->io);
2658
+ /* restore timeout if it wasn't changed */
2659
+ if (bucket->timeout == new_tmo) {
2660
+ libcouchbase_set_timeout(bucket->handle, old_tmo);
2661
+ }
1804
2662
  if (bucket->exception != Qnil) {
1805
2663
  exc = bucket->exception;
1806
2664
  bucket->exception = Qnil;
@@ -1823,7 +2681,7 @@ ensure_run(VALUE *args)
1823
2681
  /*
1824
2682
  * Run the event loop.
1825
2683
  *
1826
- * @yieldparam [Bucket] the bucket instance
2684
+ * @yieldparam [Bucket] bucket the bucket instance
1827
2685
  *
1828
2686
  * @example Use block to run the loop
1829
2687
  * c = Couchbase.new
@@ -1838,6 +2696,9 @@ ensure_run(VALUE *args)
1838
2696
  * end
1839
2697
  * c.run(&operations)
1840
2698
  *
2699
+ * @return [nil]
2700
+ *
2701
+ * @raise [Couchbase::Error::Connect] if connection closed (see {Bucket#reconnect})
1841
2702
  */
1842
2703
  static VALUE
1843
2704
  cb_bucket_run(VALUE self)
@@ -1852,7 +2713,72 @@ cb_bucket_run(VALUE self)
1852
2713
  }
1853
2714
 
1854
2715
  /*
1855
- * Unconditionally set the object in the cache
2716
+ * Unconditionally store the object in the Couchbase
2717
+ *
2718
+ * @overload set(key, value, options = {})
2719
+ *
2720
+ * @param key [String, Symbol] Key used to reference the value.
2721
+ * @param value [Object] Value to be stored
2722
+ * @param options [Hash] Options for operation.
2723
+ * @option options [Fixnum] :ttl (self.default_ttl) Expiry time for key.
2724
+ * Values larger than 30*24*60*60 seconds (30 days) are interpreted as
2725
+ * absolute times (from the epoch).
2726
+ * @option options [Fixnum] :flags (self.default_flags) Flags for storage
2727
+ * options. Flags are ignored by the server but preserved for use by the
2728
+ * client. For more info see {Bucket#default_flags}.
2729
+ * @option options [Symbol] :format (self.default_format) The
2730
+ * representation for storing the value in the bucket. For more info see
2731
+ * {Bucket#default_format}.
2732
+ * @option options [Fixnum] :cas The CAS value for an object. This value
2733
+ * created on the server and is guaranteed to be unique for each value of
2734
+ * a given key. This value is used to provide simple optimistic
2735
+ * concurrency control when multiple clients or threads try to update an
2736
+ * item simultaneously.
2737
+ *
2738
+ * @yieldparam ret [Result] the result of operation in asynchronous mode
2739
+ * (valid attributes: +error+, +operation+, +key+).
2740
+ *
2741
+ * @return [Fixnum] The CAS value of the object.
2742
+ *
2743
+ * @raise [Couchbase::Error::Connect] if connection closed (see {Bucket#reconnect}).
2744
+ * @raise [Couchbase::Error::KeyExists] if the key already exists on the
2745
+ * server.
2746
+ * @raise [Couchbase::Error::ValueFormat] if the value cannot be serialized
2747
+ * with chosen encoder, e.g. if you try to store the Hash in +:plain+
2748
+ * mode.
2749
+ * @raise [ArgumentError] when passing the block in synchronous mode
2750
+ *
2751
+ * @example Store the key which will be expired in 2 seconds using relative TTL.
2752
+ * c.set("foo", "bar", :ttl => 2)
2753
+ *
2754
+ * @example Store the key which will be expired in 2 seconds using absolute TTL.
2755
+ * c.set("foo", "bar", :ttl => Time.now.to_i + 2)
2756
+ *
2757
+ * @example Force JSON document format for value
2758
+ * c.set("foo", {"bar" => "baz}, :format => :document)
2759
+ *
2760
+ * @example Use hash-like syntax to store the value
2761
+ * c.set["foo"] = {"bar" => "baz}
2762
+ *
2763
+ * @example Use extended hash-like syntax
2764
+ * c["foo", {:flags => 0x1000, :format => :plain}] = "bar"
2765
+ * c["foo", :flags => 0x1000] = "bar" # for ruby 1.9.x only
2766
+ *
2767
+ * @example Set application specific flags (note that it will be OR-ed with format flags)
2768
+ * c.set("foo", "bar", :flags => 0x1000)
2769
+ *
2770
+ * @example Perform optimistic locking by specifying last known CAS version
2771
+ * c.set("foo", "bar", :cas => 8835713818674332672)
2772
+ *
2773
+ * @example Perform asynchronous call
2774
+ * c.run do
2775
+ * c.set("foo", "bar") do |ret|
2776
+ * ret.operation #=> :set
2777
+ * ret.success? #=> true
2778
+ * ret.key #=> "foo"
2779
+ * ret.cas
2780
+ * end
2781
+ * end
1856
2782
  */
1857
2783
  static VALUE
1858
2784
  cb_bucket_set(int argc, VALUE *argv, VALUE self)
@@ -1861,7 +2787,43 @@ cb_bucket_set(int argc, VALUE *argv, VALUE self)
1861
2787
  }
1862
2788
 
1863
2789
  /*
1864
- * Add the item to the cache, but fail if the object exists alread
2790
+ * Add the item to the database, but fail if the object exists already
2791
+ *
2792
+ * @overload add(key, value, options = {})
2793
+ * @param key [String, Symbol] Key used to reference the value.
2794
+ * @param value [Object] Value to be stored
2795
+ * @param options [Hash] Options for operation.
2796
+ * @option options [Fixnum] :ttl (self.default_ttl) Expiry time for key.
2797
+ * Values larger than 30*24*60*60 seconds (30 days) are interpreted as
2798
+ * absolute times (from the epoch).
2799
+ * @option options [Fixnum] :flags (self.default_flags) Flags for storage
2800
+ * options. Flags are ignored by the server but preserved for use by the
2801
+ * client. For more info see {Bucket#default_flags}.
2802
+ * @option options [Symbol] :format (self.default_format) The
2803
+ * representation for storing the value in the bucket. For more info see
2804
+ * {Bucket#default_format}.
2805
+ * @option options [Fixnum] :cas The CAS value for an object. This value
2806
+ * created on the server and is guaranteed to be unique for each value of
2807
+ * a given key. This value is used to provide simple optimistic
2808
+ * concurrency control when multiple clients or threads try to update an
2809
+ * item simultaneously.
2810
+ *
2811
+ * @yieldparam ret [Result] the result of operation in asynchronous mode
2812
+ * (valid attributes: +error+, +operation+, +key+).
2813
+ *
2814
+ * @return [Fixnum] The CAS value of the object.
2815
+ *
2816
+ * @raise [Couchbase::Error::Connect] if connection closed (see {Bucket#reconnect})
2817
+ * @raise [Couchbase::Error::KeyExists] if the key already exists on the
2818
+ * server
2819
+ * @raise [Couchbase::Error::ValueFormat] if the value cannot be serialized
2820
+ * with chosen encoder, e.g. if you try to store the Hash in +:plain+
2821
+ * mode.
2822
+ * @raise [ArgumentError] when passing the block in synchronous mode
2823
+ *
2824
+ * @example Add the same key twice
2825
+ * c.add("foo", "bar") #=> stored successully
2826
+ * c.add("foo", "baz") #=> will raise Couchbase::Error::KeyExists: failed to store value (key="foo", error=0x0c)
1865
2827
  */
1866
2828
  static VALUE
1867
2829
  cb_bucket_add(int argc, VALUE *argv, VALUE self)
@@ -1870,7 +2832,31 @@ cb_bucket_add(int argc, VALUE *argv, VALUE self)
1870
2832
  }
1871
2833
 
1872
2834
  /*
1873
- * Replace the existing object in the cache
2835
+ * Replace the existing object in the database
2836
+ *
2837
+ * @overload replace(key, value, options = {})
2838
+ * @param key [String, Symbol] Key used to reference the value.
2839
+ * @param value [Object] Value to be stored
2840
+ * @param options [Hash] Options for operation.
2841
+ * @option options [Fixnum] :ttl (self.default_ttl) Expiry time for key.
2842
+ * Values larger than 30*24*60*60 seconds (30 days) are interpreted as
2843
+ * absolute times (from the epoch).
2844
+ * @option options [Fixnum] :flags (self.default_flags) Flags for storage
2845
+ * options. Flags are ignored by the server but preserved for use by the
2846
+ * client. For more info see {Bucket#default_flags}.
2847
+ * @option options [Symbol] :format (self.default_format) The
2848
+ * representation for storing the value in the bucket. For more info see
2849
+ * {Bucket#default_format}.
2850
+ *
2851
+ * @return [Fixnum] The CAS value of the object.
2852
+ *
2853
+ * @raise [Couchbase::Error::Connect] if connection closed (see {Bucket#reconnect})
2854
+ * @raise [Couchbase::Error::NotFound] if the key doesn't exists
2855
+ * @raise [Couchbase::Error::KeyExists] on CAS mismatch
2856
+ * @raise [ArgumentError] when passing the block in synchronous mode
2857
+ *
2858
+ * @example Replacing missing key
2859
+ * c.replace("foo", "baz") #=> will raise Couchbase::Error::NotFound: failed to store value (key="foo", error=0x0d)
1874
2860
  */
1875
2861
  static VALUE
1876
2862
  cb_bucket_replace(int argc, VALUE *argv, VALUE self)
@@ -1880,6 +2866,67 @@ cb_bucket_replace(int argc, VALUE *argv, VALUE self)
1880
2866
 
1881
2867
  /*
1882
2868
  * Append this object to the existing object
2869
+ *
2870
+ * @note This operation is kind of data-aware from server point of view.
2871
+ * This mean that the server treats value as binary stream and just
2872
+ * perform concatenation, therefore it won't work with +:marshal+ and
2873
+ * +:document+ formats, because of lack of knowledge how to merge values
2874
+ * in these formats. See Bucket#cas for workaround.
2875
+ *
2876
+ * @overload append(key, value, options = {})
2877
+ * @param key [String, Symbol] Key used to reference the value.
2878
+ * @param value [Object] Value to be stored
2879
+ * @param options [Hash] Options for operation.
2880
+ * @option options [Fixnum] :cas The CAS value for an object. This value
2881
+ * created on the server and is guaranteed to be unique for each value of
2882
+ * a given key. This value is used to provide simple optimistic
2883
+ * concurrency control when multiple clients or threads try to update an
2884
+ * item simultaneously.
2885
+ * @option options [Symbol] :format (self.default_format) The
2886
+ * representation for storing the value in the bucket. For more info see
2887
+ * {Bucket#default_format}.
2888
+ *
2889
+ * @return [Fixnum] The CAS value of the object.
2890
+ *
2891
+ * @raise [Couchbase::Error::Connect] if connection closed (see {Bucket#reconnect})
2892
+ * @raise [Couchbase::Error::KeyExists] on CAS mismatch
2893
+ * @raise [Couchbase::Error::NotStored] if the key doesn't exist
2894
+ * @raise [ArgumentError] when passing the block in synchronous mode
2895
+ *
2896
+ * @example Simple append
2897
+ * c.set("foo", "aaa")
2898
+ * c.append("foo", "bbb")
2899
+ * c.get("foo") #=> "aaabbb"
2900
+ *
2901
+ * @example Implementing sets using append
2902
+ * def set_add(key, *values)
2903
+ * encoded = values.flatten.map{|v| "+#{v} "}.join
2904
+ * append(key, encoded)
2905
+ * end
2906
+ *
2907
+ * def set_remove(key, *values)
2908
+ * encoded = values.flatten.map{|v| "-#{v} "}.join
2909
+ * append(key, encoded)
2910
+ * end
2911
+ *
2912
+ * def set_get(key)
2913
+ * encoded = get(key)
2914
+ * ret = Set.new
2915
+ * encoded.split(' ').each do |v|
2916
+ * op, val = v[0], v[1..-1]
2917
+ * case op
2918
+ * when "-"
2919
+ * ret.delete(val)
2920
+ * when "+"
2921
+ * ret.add(val)
2922
+ * end
2923
+ * end
2924
+ * ret
2925
+ * end
2926
+ *
2927
+ * @example Using optimistic locking. The operation will fail on CAS mismatch
2928
+ * ver = c.set("foo", "aaa")
2929
+ * c.append("foo", "bbb", :cas => ver)
1883
2930
  */
1884
2931
  static VALUE
1885
2932
  cb_bucket_append(int argc, VALUE *argv, VALUE self)
@@ -1889,6 +2936,46 @@ cb_bucket_append(int argc, VALUE *argv, VALUE self)
1889
2936
 
1890
2937
  /*
1891
2938
  * Prepend this object to the existing object
2939
+ *
2940
+ * @note This operation is kind of data-aware from server point of view.
2941
+ * This mean that the server treats value as binary stream and just
2942
+ * perform concatenation, therefore it won't work with +:marshal+ and
2943
+ * +:document+ formats, because of lack of knowledge how to merge values
2944
+ * in these formats. See Bucket#cas for workaround.
2945
+ *
2946
+ * @overload prepend(key, value, options = {})
2947
+ * @param key [String, Symbol] Key used to reference the value.
2948
+ * @param value [Object] Value to be stored
2949
+ * @param options [Hash] Options for operation.
2950
+ * @option options [Fixnum] :cas The CAS value for an object. This value
2951
+ * created on the server and is guaranteed to be unique for each value of
2952
+ * a given key. This value is used to provide simple optimistic
2953
+ * concurrency control when multiple clients or threads try to update an
2954
+ * item simultaneously.
2955
+ * @option options [Symbol] :format (self.default_format) The
2956
+ * representation for storing the value in the bucket. For more info see
2957
+ * {Bucket#default_format}.
2958
+ *
2959
+ * @raise [Couchbase::Error::Connect] if connection closed (see {Bucket#reconnect})
2960
+ * @raise [Couchbase::Error::KeyExists] on CAS mismatch
2961
+ * @raise [Couchbase::Error::NotStored] if the key doesn't exist
2962
+ * @raise [ArgumentError] when passing the block in synchronous mode
2963
+ *
2964
+ * @example Simple prepend example
2965
+ * c.set("foo", "aaa")
2966
+ * c.prepend("foo", "bbb")
2967
+ * c.get("foo") #=> "bbbaaa"
2968
+ *
2969
+ * @example Using explicit format option
2970
+ * c.default_format #=> :document
2971
+ * c.set("foo", {"y" => "z"})
2972
+ * c.prepend("foo", '[', :format => :plain)
2973
+ * c.append("foo", ', {"z": "y"}]', :format => :plain)
2974
+ * c.get("foo") #=> [{"y"=>"z"}, {"z"=>"y"}]
2975
+ *
2976
+ * @example Using optimistic locking. The operation will fail on CAS mismatch
2977
+ * ver = c.set("foo", "aaa")
2978
+ * c.prepend("foo", "bbb", :cas => ver)
1892
2979
  */
1893
2980
  static VALUE
1894
2981
  cb_bucket_prepend(int argc, VALUE *argv, VALUE self)
@@ -1910,6 +2997,28 @@ cb_bucket_aset(int argc, VALUE *argv, VALUE self)
1910
2997
  return cb_bucket_set(argc, argv, self);
1911
2998
  }
1912
2999
 
3000
+ /*
3001
+ * Close the connection to the cluster
3002
+ *
3003
+ * @return [true]
3004
+ *
3005
+ * @raise [Couchbase::Error::Connect] if connection closed (see {Bucket#reconnect})
3006
+ */
3007
+ static VALUE
3008
+ cb_bucket_disconnect(VALUE self)
3009
+ {
3010
+ bucket_t *bucket = DATA_PTR(self);
3011
+
3012
+ if (bucket->handle) {
3013
+ libcouchbase_destroy(bucket->handle);
3014
+ bucket->handle = NULL;
3015
+ bucket->io = NULL;
3016
+ return Qtrue;
3017
+ } else {
3018
+ rb_raise(eConnectError, "closed connection");
3019
+ }
3020
+ }
3021
+
1913
3022
  /*
1914
3023
  * Check if result of operation was successful.
1915
3024
  *
@@ -1922,10 +3031,15 @@ cb_result_success_p(VALUE self)
1922
3031
  return RTEST(rb_ivar_get(self, id_iv_error)) ? Qfalse : Qtrue;
1923
3032
  }
1924
3033
 
3034
+ /*
3035
+ * Returns a string containing a human-readable representation of the Result.
3036
+ *
3037
+ * @return [String]
3038
+ */
1925
3039
  static VALUE
1926
3040
  cb_result_inspect(VALUE self)
1927
3041
  {
1928
- VALUE str, attr, errno;
3042
+ VALUE str, attr, error;
1929
3043
  char buf[100];
1930
3044
 
1931
3045
  str = rb_str_buf_new2("#<");
@@ -1935,12 +3049,12 @@ cb_result_inspect(VALUE self)
1935
3049
 
1936
3050
  attr = rb_ivar_get(self, id_iv_error);
1937
3051
  if (RTEST(attr)) {
1938
- errno = rb_ivar_get(attr, id_iv_error);
3052
+ error = rb_ivar_get(attr, id_iv_error);
1939
3053
  } else {
1940
- errno = INT2FIX(0);
3054
+ error = INT2FIX(0);
1941
3055
  }
1942
3056
  rb_str_buf_cat2(str, " error=0x");
1943
- rb_str_append(str, rb_funcall(errno, id_to_s, 1, INT2FIX(16)));
3057
+ rb_str_append(str, rb_funcall(error, id_to_s, 1, INT2FIX(16)));
1944
3058
 
1945
3059
  attr = rb_ivar_get(self, id_iv_key);
1946
3060
  if (RTEST(attr)) {
@@ -1975,6 +3089,7 @@ cb_result_inspect(VALUE self)
1975
3089
  Init_couchbase_ext(void)
1976
3090
  {
1977
3091
  mJSON = rb_const_get(rb_cObject, rb_intern("JSON"));
3092
+ mURI = rb_const_get(rb_cObject, rb_intern("URI"));
1978
3093
  mMarshal = rb_const_get(rb_cObject, rb_intern("Marshal"));
1979
3094
  mCouchbase = rb_define_module("Couchbase");
1980
3095
 
@@ -1985,6 +3100,9 @@ Init_couchbase_ext(void)
1985
3100
  /* Document-class: Couchbase::Error::Auth
1986
3101
  * Authentication error */
1987
3102
  eAuthError = rb_define_class_under(mError, "Auth", eBaseError);
3103
+ /* Document-class: Couchbase::Error::BucketNotFound
3104
+ * The given bucket not found in the cluster */
3105
+ eBucketNotFoundError = rb_define_class_under(mError, "BucketNotFound", eBaseError);
1988
3106
  /* Document-class: Couchbase::Error::Busy
1989
3107
  * The cluster is too busy now. Try again later */
1990
3108
  eBusyError = rb_define_class_under(mError, "Busy", eBaseError);
@@ -2045,6 +3163,12 @@ Init_couchbase_ext(void)
2045
3163
  /* Document-class: Couchbase::Error::Protocol
2046
3164
  * Protocol error */
2047
3165
  eProtocolError = rb_define_class_under(mError, "Protocol", eBaseError);
3166
+ /* Document-class: Couchbase::Error::Timeout
3167
+ * Timeout error */
3168
+ eTimeoutError = rb_define_class_under(mError, "Timeout", eBaseError);
3169
+ /* Document-class: Couchbase::Error::Connect
3170
+ * Connect error */
3171
+ eConnectError = rb_define_class_under(mError, "Connect", eBaseError);
2048
3172
 
2049
3173
  /* Document-method: error
2050
3174
  * @return [Boolean] the error code from libcouchbase */
@@ -2064,7 +3188,7 @@ Init_couchbase_ext(void)
2064
3188
  id_iv_operation = rb_intern("@operation");
2065
3189
 
2066
3190
  /* Document-class: Couchbase::Result
2067
- * Protocol error */
3191
+ * The object which yielded to asynchronous callbacks */
2068
3192
  cResult = rb_define_class_under(mCouchbase, "Result", rb_cObject);
2069
3193
  rb_define_method(cResult, "inspect", cb_result_inspect, 0);
2070
3194
  rb_define_method(cResult, "success?", cb_result_success_p, 0);
@@ -2072,7 +3196,7 @@ Init_couchbase_ext(void)
2072
3196
  * @return [Symbol] */
2073
3197
  rb_define_attr(cResult, "operation", 1, 0);
2074
3198
  /* Document-method: error
2075
- * @return [Error::Base] */
3199
+ * @return [Couchbase::Error::Base] */
2076
3200
  rb_define_attr(cResult, "error", 1, 0);
2077
3201
  /* Document-method: key
2078
3202
  * @return [String] */
@@ -2100,17 +3224,35 @@ Init_couchbase_ext(void)
2100
3224
  * Couchbase. */
2101
3225
  cBucket = rb_define_class_under(mCouchbase, "Bucket", rb_cObject);
2102
3226
  object_space = rb_hash_new();
3227
+ /* @private Hack to avoid GC in some cases */
2103
3228
  rb_define_const(cBucket, "OBJECT_SPACE", object_space);
2104
3229
 
3230
+ /* 0x03: Bitmask for flag bits responsible for format */
2105
3231
  rb_define_const(cBucket, "FMT_MASK", INT2FIX(FMT_MASK));
3232
+ /* 0x00: Document format. The (default) format supports most of ruby
3233
+ * types which could be mapped to JSON data (hashes, arrays, strings,
3234
+ * numbers). Future version will be able to run map/reduce queries on
3235
+ * the values in the document form (hashes). */
2106
3236
  rb_define_const(cBucket, "FMT_DOCUMENT", INT2FIX(FMT_DOCUMENT));
3237
+ /* 0x01: Marshal format. The format which supports transparent
3238
+ * serialization of ruby objects with standard <tt>Marshal.dump</tt> and
3239
+ * <tt>Marhal.load</tt> methods. */
2107
3240
  rb_define_const(cBucket, "FMT_MARSHAL", INT2FIX(FMT_MARSHAL));
3241
+ /* 0x02: Plain format. The format which force client don't apply any
3242
+ * conversions to the value, but it should be passed as String. It
3243
+ * could be useful for building custom algorithms or formats. For
3244
+ * example implement set:
3245
+ * http://dustin.github.com/2011/02/17/memcached-set.html */
2108
3246
  rb_define_const(cBucket, "FMT_PLAIN", INT2FIX(FMT_PLAIN));
2109
3247
 
2110
3248
  rb_define_singleton_method(cBucket, "new", cb_bucket_new, -1);
2111
3249
 
2112
3250
  rb_define_method(cBucket, "initialize", cb_bucket_init, -1);
2113
3251
  rb_define_method(cBucket, "inspect", cb_bucket_inspect, 0);
3252
+
3253
+ /* Document-method: seqno
3254
+ * The number of scheduled commands */
3255
+ /* rb_define_attr(cBucket, "seqno", 1, 0); */
2114
3256
  rb_define_method(cBucket, "seqno", cb_bucket_seqno, 0);
2115
3257
 
2116
3258
  rb_define_method(cBucket, "add", cb_bucket_add, -1);
@@ -2124,39 +3266,27 @@ Init_couchbase_ext(void)
2124
3266
  rb_define_method(cBucket, "delete", cb_bucket_delete, -1);
2125
3267
  rb_define_method(cBucket, "stats", cb_bucket_stats, -1);
2126
3268
  rb_define_method(cBucket, "flush", cb_bucket_flush, 0);
3269
+ rb_define_method(cBucket, "version", cb_bucket_version, 0);
2127
3270
  rb_define_method(cBucket, "incr", cb_bucket_incr, -1);
2128
3271
  rb_define_method(cBucket, "decr", cb_bucket_decr, -1);
3272
+ rb_define_method(cBucket, "disconnect", cb_bucket_disconnect, 0);
3273
+ rb_define_method(cBucket, "reconnect", cb_bucket_reconnect, -1);
2129
3274
 
2130
3275
  rb_define_alias(cBucket, "decrement", "decr");
2131
3276
  rb_define_alias(cBucket, "increment", "incr");
2132
3277
 
2133
3278
  rb_define_alias(cBucket, "[]", "get");
2134
- /* rb_define_alias(cBucket, "[]=", "set"); */
3279
+ rb_define_alias(cBucket, "[]=", "set");
2135
3280
  rb_define_method(cBucket, "[]=", cb_bucket_aset, -1);
2136
3281
 
2137
- /* Document-method: async?
2138
- * Flag specifying if the connection asynchronous.
2139
- *
2140
- * By default all operations are synchronous and block waiting for
2141
- * results, but you can make them asynchronous and run event loop
2142
- * explicitly. (see Bucket#run)
2143
- *
2144
- * @example Return value of #get operation depending on async flag
2145
- * connection = Connection.new
2146
- * connection.async? #=> false
2147
- *
2148
- * connection.run do |conn|
2149
- * conn.async? #=> true
2150
- * end
2151
- *
2152
- * @return [Boolean] */
3282
+ rb_define_method(cBucket, "connected?", cb_bucket_connected_p, 0);
2153
3283
  rb_define_method(cBucket, "async?", cb_bucket_async_p, 0);
2154
3284
 
2155
3285
  /* Document-method: quiet
2156
3286
  * Flag specifying behaviour for operations on missing keys
2157
3287
  *
2158
3288
  * If it is +true+, the operations will silently return +nil+ or +false+
2159
- * instead of raising Couchbase::Error::NotFoundError.
3289
+ * instead of raising {Couchbase::Error::NotFound}.
2160
3290
  *
2161
3291
  * @example Hiding cache miss (considering "miss" key is not stored)
2162
3292
  * connection.quiet = true
@@ -2164,13 +3294,13 @@ Init_couchbase_ext(void)
2164
3294
  *
2165
3295
  * @example Raising errors on miss (considering "miss" key is not stored)
2166
3296
  * connection.quiet = false
2167
- * connection.get("miss") #=> will raise Couchbase::Error::NotFoundError
3297
+ * connection.get("miss") #=> will raise Couchbase::Error::NotFound
2168
3298
  *
2169
3299
  * @return [Boolean] */
2170
- rb_define_attr(cBucket, "quiet", 1, 1);
3300
+ /* rb_define_attr(cBucket, "quiet", 1, 1); */
3301
+ rb_define_method(cBucket, "quiet", cb_bucket_quiet_get, 0);
2171
3302
  rb_define_method(cBucket, "quiet=", cb_bucket_quiet_set, 1);
2172
3303
  rb_define_alias(cBucket, "quiet?", "quiet");
2173
- id_iv_quiet = rb_intern("@quiet");
2174
3304
 
2175
3305
  /* Document-method: default_flags
2176
3306
  * Default flags for new values.
@@ -2187,9 +3317,9 @@ Init_couchbase_ext(void)
2187
3317
  * @note Amending format bit will also change #default_format value
2188
3318
  *
2189
3319
  * @return [Fixnum] the effective flags */
2190
- rb_define_attr(cBucket, "default_flags", 1, 1);
3320
+ /* rb_define_attr(cBucket, "default_flags", 1, 1); */
3321
+ rb_define_method(cBucket, "default_flags", cb_bucket_default_flags_get, 0);
2191
3322
  rb_define_method(cBucket, "default_flags=", cb_bucket_default_flags_set, 1);
2192
- id_iv_default_flags = rb_intern("@default_flags");
2193
3323
 
2194
3324
  /* Document-method: default_format
2195
3325
  * Default format for new values.
@@ -2202,7 +3332,7 @@ Init_couchbase_ext(void)
2202
3332
  * Here is some notes regarding how to choose the format:
2203
3333
  *
2204
3334
  * * <tt>:document</tt> (default) format supports most of ruby types
2205
- * which could be mapped to JSON data (hashes, arrays, string,
3335
+ * which could be mapped to JSON data (hashes, arrays, strings,
2206
3336
  * numbers). Future version will be able to run map/reduce queries on
2207
3337
  * the values in the document form (hashes).
2208
3338
  *
@@ -2224,9 +3354,18 @@ Init_couchbase_ext(void)
2224
3354
  * @note Amending default_format will also change #default_flags value
2225
3355
  *
2226
3356
  * @return [Symbol] the effective format */
2227
- rb_define_attr(cBucket, "default_format", 1, 1);
3357
+ /* rb_define_attr(cBucket, "default_format", 1, 1); */
3358
+ rb_define_method(cBucket, "default_format", cb_bucket_default_format_get, 0);
2228
3359
  rb_define_method(cBucket, "default_format=", cb_bucket_default_format_set, 1);
2229
- id_iv_default_format = rb_intern("@default_format");
3360
+
3361
+ /* Document-method: timeout
3362
+ * @return [Fixnum] The timeout for the operations. The client will
3363
+ * raise {Couchbase::Error::Timeout} exception for all commands which
3364
+ * weren't completed in given timeslot. */
3365
+ /* rb_define_attr(cBucket, "timeout", 1, 1); */
3366
+ rb_define_method(cBucket, "timeout", cb_bucket_timeout_get, 0);
3367
+ rb_define_method(cBucket, "timeout=", cb_bucket_timeout_set, 1);
3368
+
2230
3369
  /* Document-method: on_error
2231
3370
  * Error callback for asynchronous mode.
2232
3371
  *
@@ -2249,54 +3388,44 @@ Init_couchbase_ext(void)
2249
3388
  * ...
2250
3389
  *
2251
3390
  * @return [Proc] the effective callback */
2252
- rb_define_attr(cBucket, "on_error", 1, 1);
3391
+ /* rb_define_attr(cBucket, "on_error", 1, 1); */
2253
3392
  rb_define_method(cBucket, "on_error", cb_bucket_on_error_get, 0);
2254
3393
  rb_define_method(cBucket, "on_error=", cb_bucket_on_error_set, 1);
2255
- id_iv_on_error = rb_intern("@on_error");
2256
-
2257
- /* Document-method: url
2258
- * @return [String] the address of the cluster management interface. */
2259
- rb_define_attr(cBucket, "url", 1, 0);
2260
- id_iv_url = rb_intern("@url");
2261
- /* Document-method: hostname
2262
- * @return [String] the host name of the management interface (default: "localhost") */
2263
- rb_define_attr(cBucket, "hostname", 1, 0);
2264
- id_iv_hostname = rb_intern("@hostname");
2265
- /* Document-method: port
2266
- * @return [Fixnum] the port number of the management interface (default: 8091) */
2267
- rb_define_attr(cBucket, "port", 1, 0);
2268
- id_iv_port = rb_intern("@port");
2269
- /* Document-method: authority
2270
- * @return [String] host with port. */
2271
- rb_define_attr(cBucket, "authority", 1, 0);
2272
- id_iv_authority = rb_intern("@authority");
2273
- /* Document-method: bucket
2274
- * @return [String] the bucket name */
2275
- rb_define_attr(cBucket, "bucket", 1, 0);
3394
+
3395
+ /* rb_define_attr(cBucket, "url", 1, 0); */
3396
+ rb_define_method(cBucket, "url", cb_bucket_url_get, 0);
3397
+ /* rb_define_attr(cBucket, "hostname", 1, 0); */
3398
+ rb_define_method(cBucket, "hostname", cb_bucket_hostname_get, 0);
3399
+ /* rb_define_attr(cBucket, "port", 1, 0); */
3400
+ rb_define_method(cBucket, "port", cb_bucket_port_get, 0);
3401
+ /* rb_define_attr(cBucket, "authority", 1, 0); */
3402
+ rb_define_method(cBucket, "authority", cb_bucket_authority_get, 0);
3403
+ /* rb_define_attr(cBucket, "bucket", 1, 0); */
3404
+ rb_define_method(cBucket, "bucket", cb_bucket_bucket_get, 0);
2276
3405
  rb_define_alias(cBucket, "name", "bucket");
2277
- id_iv_bucket = rb_intern("@bucket");
2278
- /* Document-method: pool
2279
- * @return [String] the pool name (usually "default") */
2280
- rb_define_attr(cBucket, "pool", 1, 0);
2281
- id_iv_pool = rb_intern("@pool");
2282
- /* Document-method: username
2283
- * @return [String] the username for protected buckets (usually matches
2284
- * the bucket name) */
2285
- rb_define_attr(cBucket, "username", 1, 0);
2286
- id_iv_username = rb_intern("@username");
2287
- /* Document-method: password
2288
- * @return [String] the password for protected buckets */
2289
- rb_define_attr(cBucket, "password", 1, 0);
2290
- id_iv_password = rb_intern("@password");
3406
+ /* rb_define_attr(cBucket, "pool", 1, 0); */
3407
+ rb_define_method(cBucket, "pool", cb_bucket_pool_get, 0);
3408
+ /* rb_define_attr(cBucket, "username", 1, 0); */
3409
+ rb_define_method(cBucket, "username", cb_bucket_username_get, 0);
3410
+ /* rb_define_attr(cBucket, "password", 1, 0); */
3411
+ rb_define_method(cBucket, "password", cb_bucket_password_get, 0);
2291
3412
 
2292
3413
  /* Define symbols */
2293
3414
  id_arity = rb_intern("arity");
2294
3415
  id_call = rb_intern("call");
2295
- id_load = rb_intern("load");
2296
3416
  id_dump = rb_intern("dump");
2297
3417
  id_flatten_bang = rb_intern("flatten!");
2298
3418
  id_has_key_p = rb_intern("has_key?");
3419
+ id_host = rb_intern("host");
3420
+ id_load = rb_intern("load");
3421
+ id_match = rb_intern("match");
3422
+ id_parse = rb_intern("parse");
3423
+ id_password = rb_intern("password");
3424
+ id_path = rb_intern("path");
3425
+ id_port = rb_intern("port");
3426
+ id_scheme = rb_intern("scheme");
2299
3427
  id_to_s = rb_intern("to_s");
3428
+ id_user = rb_intern("user");
2300
3429
 
2301
3430
  sym_add = ID2SYM(rb_intern("add"));
2302
3431
  sym_append = ID2SYM(rb_intern("append"));
@@ -2306,6 +3435,7 @@ Init_couchbase_ext(void)
2306
3435
  sym_decrement = ID2SYM(rb_intern("decrement"));
2307
3436
  sym_default_flags = ID2SYM(rb_intern("default_flags"));
2308
3437
  sym_default_format = ID2SYM(rb_intern("default_format"));
3438
+ sym_default_ttl = ID2SYM(rb_intern("default_ttl"));
2309
3439
  sym_delete = ID2SYM(rb_intern("delete"));
2310
3440
  sym_document = ID2SYM(rb_intern("document"));
2311
3441
  sym_extended = ID2SYM(rb_intern("extended"));
@@ -2326,7 +3456,9 @@ Init_couchbase_ext(void)
2326
3456
  sym_replace = ID2SYM(rb_intern("replace"));
2327
3457
  sym_set = ID2SYM(rb_intern("set"));
2328
3458
  sym_stats = ID2SYM(rb_intern("stats"));
3459
+ sym_timeout = ID2SYM(rb_intern("timeout"));
2329
3460
  sym_touch = ID2SYM(rb_intern("touch"));
2330
3461
  sym_ttl = ID2SYM(rb_intern("ttl"));
2331
3462
  sym_username = ID2SYM(rb_intern("username"));
3463
+ sym_version = ID2SYM(rb_intern("version"));
2332
3464
  }