couchbase 1.1.5 → 1.2.0.beta

Sign up to get free protection for your applications and to get access to all the features.
Files changed (52) hide show
  1. data/.gitignore +2 -1
  2. data/.travis.yml +12 -1
  3. data/HISTORY.markdown +112 -1
  4. data/README.markdown +149 -6
  5. data/couchbase.gemspec +5 -1
  6. data/ext/couchbase_ext/.gitignore +4 -0
  7. data/ext/couchbase_ext/arguments.c +973 -0
  8. data/ext/couchbase_ext/arithmetic.c +322 -0
  9. data/ext/couchbase_ext/bucket.c +1092 -0
  10. data/ext/couchbase_ext/couchbase_ext.c +618 -3247
  11. data/ext/couchbase_ext/couchbase_ext.h +519 -0
  12. data/ext/couchbase_ext/delete.c +167 -0
  13. data/ext/couchbase_ext/extconf.rb +24 -5
  14. data/ext/couchbase_ext/get.c +301 -0
  15. data/ext/couchbase_ext/gethrtime.c +124 -0
  16. data/ext/couchbase_ext/http.c +402 -0
  17. data/ext/couchbase_ext/observe.c +174 -0
  18. data/ext/couchbase_ext/result.c +126 -0
  19. data/ext/couchbase_ext/stats.c +169 -0
  20. data/ext/couchbase_ext/store.c +522 -0
  21. data/ext/couchbase_ext/timer.c +192 -0
  22. data/ext/couchbase_ext/touch.c +190 -0
  23. data/ext/couchbase_ext/unlock.c +180 -0
  24. data/ext/couchbase_ext/utils.c +471 -0
  25. data/ext/couchbase_ext/version.c +147 -0
  26. data/lib/action_dispatch/middleware/session/couchbase_store.rb +38 -0
  27. data/lib/active_support/cache/couchbase_store.rb +356 -0
  28. data/lib/couchbase.rb +24 -3
  29. data/lib/couchbase/bucket.rb +372 -9
  30. data/lib/couchbase/result.rb +26 -0
  31. data/lib/couchbase/utils.rb +59 -0
  32. data/lib/couchbase/version.rb +1 -1
  33. data/lib/couchbase/view.rb +305 -0
  34. data/lib/couchbase/view_row.rb +230 -0
  35. data/lib/ext/multi_json_fix.rb +47 -0
  36. data/lib/rack/session/couchbase.rb +104 -0
  37. data/tasks/compile.rake +5 -14
  38. data/test/setup.rb +6 -2
  39. data/test/test_arithmetic.rb +32 -2
  40. data/test/test_async.rb +18 -4
  41. data/test/test_bucket.rb +11 -1
  42. data/test/test_cas.rb +13 -3
  43. data/test/test_couchbase_rails_cache_store.rb +294 -0
  44. data/test/test_delete.rb +60 -3
  45. data/test/test_format.rb +28 -17
  46. data/test/test_get.rb +91 -14
  47. data/test/test_store.rb +31 -1
  48. data/test/{test_flush.rb → test_timer.rb} +11 -18
  49. data/test/test_touch.rb +33 -5
  50. data/test/test_unlock.rb +120 -0
  51. data/test/test_utils.rb +26 -0
  52. metadata +101 -8
data/.gitignore CHANGED
@@ -1,6 +1,7 @@
1
+ *.bundle
1
2
  *.gem
2
3
  *.so
3
- .bundle
4
+ .timestamp
4
5
  .yardoc
5
6
  Gemfile.lock
6
7
  core*
@@ -1,6 +1,13 @@
1
1
  before_install:
2
+ - sudo rm -rf /etc/apt/sources.list.d/couchdb-ppa-source.list
3
+ - sudo rm -rf /etc/apt/sources.list.d/mongodb.list
4
+ - sudo rm -rf /etc/apt/sources.list.d/datastax-source.list
5
+ - sudo rm -rf /etc/apt/sources.list.d/rabbitmq-source.list
6
+ - sudo rm -rf /etc/apt/sources.list.d/mapopa-source.list
7
+ - sudo rm -rf /etc/apt/sources.list.d/webupd8team-java-ppa-source.list
8
+ - sudo rm -rf /etc/apt/sources.list.d/maven3-ppa-source.list
2
9
  - 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
10
+ - echo deb http://packages.couchbase.com/snapshot/ubuntu oneiric oneiric/main | sudo tee /etc/apt/sources.list.d/couchbase.list
4
11
  - sudo apt-get update
5
12
  - sudo apt-get -y install libevent-dev libvbucket-dev libcouchbase-dev
6
13
 
@@ -9,3 +16,7 @@ rvm:
9
16
  - 1.9.2
10
17
  - 1.9.3
11
18
  - ree
19
+
20
+ notifications:
21
+ email:
22
+ - sdk_dev@couchbase.com
@@ -1,6 +1,117 @@
1
- ## 1.1.5 / 2012-09-17
1
+ ## 1.2.0.beta / 2012-09-18
2
2
 
3
+ * RCBC-70 return binary keys using Encoding.external value (thanks to Alex Leverington)
4
+ * Create new key object only if it is necessary
5
+ * Switch to rbenv because RVM doesn't work with tclsh
6
+ * [backport] RCBC-37 Bootstrapping using multiple nodes
7
+ * React on HTTP level errors in view request
8
+ * Fix typo: check for MultiJson.decode
9
+ * Use unified HTTP function from the latest libcouchbase
10
+ * Expose HTTP headers
11
+ * Update views to meet latest server changes
12
+ * Add support for spatial views
13
+ * Use updated libcouchbase_cancel_http_request()
14
+ * Workaround query issue on group=false&reduce=false
15
+ * Update windows build
16
+ * Bump version 1.1.4
17
+ * Refactor the C extension
18
+ * Fix CAS conversion for Bucket#delete method for 32-bit systems
19
+ * RCBC-28 Implement Bucket#unlock
20
+ * CCBC-98 Expose client temporary failure error
21
+ * Fix warnings in doc generator and specify return values
22
+ * Fix -Wreturn-type warning
23
+ * Add attribute reader for Error::Base status code
24
+ * Unset RUBYOPT to avoid issues with bundler
25
+ * Ignore shared object on macos
26
+ * RCBC-79 Use RESTful flush
27
+ * Fix build under bundler on MacOS
3
28
  * RCBC-81 Protect against NoMethodError
29
+ * Fixup MAKEFILE_CONFIG which is copy of the CONFIG
30
+ * RCBC-81 Protect against NoMethodError
31
+ * Update windows build
32
+
33
+ ## 1.2.0.dp6 / 2012-07-28
34
+
35
+ * Inherit StandardError instead RuntimeError for errors
36
+ * Use renamed functions for view requests
37
+ * Depend on yard-xml plugin to dump docs into single XML
38
+ * No need to check for NULL before deallocating memory
39
+ * RCBC-37 Bootstrapping using multiple nodes
40
+ * More docs and examples on views (fixes RCBC-43)
41
+ * RCBC-40 Fix Bucket#cas operation behaviour in async mode
42
+ * RCBC-39 Allow to specify delta for incr/decr in options
43
+ * Update README
44
+ * Apply timeout value before connection
45
+ * Clarify connection exceptions
46
+ * Remove seqno kludge
47
+ * rb_hash_delete() function could incorrectly detect block presence
48
+ * Add example with in-URL credentials
49
+ * RCBC-57 Expose timers API from libcouchbase
50
+ * RCBC-50 Allow to read keys from replica
51
+ * RCBC-6 Implement OBSERVE command
52
+ * Expose number of replicas to the user
53
+ * Notify about observe batch finish in async mode
54
+ * Separate memory errors for client and server
55
+ * Prefix error message from views with "SERVER: "
56
+ * Remove timeout hack
57
+ * RCBC-49 Bucket#observe_and_wait primitive
58
+ * RCBC-47 Allow to skip username for protected buckets
59
+ * Use allocators instead of singleton methods
60
+ * Check RDATA()->dfree to ensure object type
61
+ * Fix observe_and_wait in async mode
62
+ * Fill 'operation' in observe_and_wait Result object
63
+ * Fix extraction Hash with keys in observe_and_wait
64
+ * RCBC-49 :observe option for storage functions
65
+ * Fix timeout test
66
+ * Mention couchbase.com in install errors
67
+ * Make Bucket#observe_and_wait more 1.8.7 friendly
68
+
69
+ ## 1.2.0.dp5 / 2012-06-15
70
+
71
+ * Allow to force assembling result Hash for multi-get
72
+ * Fix documentation for :ttl option in Bucket#cas
73
+ * Implement key prefix (simple namespacing)
74
+ * Implement cache store adapter for Rails
75
+ * Integrate with Rack and Rails session store
76
+
77
+ ## 1.2.0.dp4 / 2012-06-07
78
+
79
+ * RCBC-36 Fix segfault
80
+ * Comment out unpredictable test
81
+ * Update replace documentation: it accepts :cas option
82
+
83
+ ## 1.2.0.dp3 / 2012-06-06
84
+
85
+ * Fix for multi_json < 1.3.3
86
+ * Break out from event loop for non-chunked responses (fix creating
87
+ design create)
88
+
89
+ ## 1.2.0.dp2 / 2012-06-05
90
+
91
+ * RCBC-31 Make Bucket#get more consistent
92
+ * Use monotonic high resolution clock
93
+ * Implement threshold for outgoing commands
94
+ * Allow to stop event loop from ruby
95
+ * RCBC-35 Fix the params escaping
96
+ * RCBC-34 Use multi_json gem
97
+ * Allow to block and wait for part of the requests
98
+ * Fix view iterator. It doesn't lock event loop anymore
99
+ * Specify HTTP method when body is set for View request
100
+ * Define views only if "views" key presented
101
+ * Use plain structs instead of typedefs
102
+ * Use debugger gem for 1.9.x rubies
103
+ * Use latest stable build of libcouchbase for travis-ci
104
+ * Require yajl as development dependency
105
+ * Implement get with lock operation
106
+ * Update docs
107
+
108
+ ## 1.2.0.dp / 2012-04-06
109
+
110
+ * Properly handle hashes as Couchbase.connection_options
111
+ * Implement views
112
+ * Use verbose mode by default throwing exceptions on NOT_FOUND errors.
113
+ This means that quiet attribute is false now on new connections.
114
+ * Doc fixes
4
115
 
5
116
  ## 1.1.4 / 2012-08-30
6
117
 
@@ -1,4 +1,4 @@
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)
1
+ # Couchbase Ruby Client [![Build Status](https://secure.travis-ci.org/couchbase/couchbase-ruby-client.png?branch=master)](http://travis-ci.org/couchbase/couchbase-ruby-client)
2
2
 
3
3
  This is the official client library for use with Couchbase Server.
4
4
 
@@ -18,14 +18,15 @@ libcouchbase doesn't take much effort.
18
18
 
19
19
  $ brew install libcouchbase
20
20
 
21
- Or if [our pull requests][4] for isn't yet merged:
21
+ The official homebrew repository contains only stable versions of
22
+ libvbucket and libcouchbase, if you need preview, take a look at
23
+ Couchbase's fork: https://github.com/couchbase/homebrew
22
24
 
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
+ $ brew install https://raw.github.com/couchbase/homebrew/preview/Library/Formula/libcouchbase.rb
25
26
 
26
27
  ### Debian (Ubuntu)
27
28
 
28
- Add the appropriate line to /etc/apt/sources.list.d/couchbase.list for
29
+ Add the appropriate line to `/etc/apt/sources.list.d/couchbase.list` for
29
30
  your OS release:
30
31
 
31
32
  # Ubuntu 11.10 Oneiric Ocelot (Debian unstable)
@@ -42,6 +43,15 @@ Then install them
42
43
 
43
44
  $ sudo apt-get update && sudo apt-get install libcouchbase-dev
44
45
 
46
+ Again, if you need preview versions, just use another repositories in
47
+ your `couchbase.list`
48
+
49
+ # Ubuntu 11.10 Oneiric Ocelot (Debian unstable)
50
+ deb http://packages.couchbase.com/preview/ubuntu oneiric oneiric/main
51
+
52
+ # Ubuntu 10.04 Lucid Lynx (Debian stable or testing)
53
+ deb http://packages.couchbase.com/preview/ubuntu lucid lucid/main
54
+
45
55
  ### Centos (Redhat and rpm-based systems)
46
56
 
47
57
  Add these lines to /etc/yum.repos.d/couchbase.repo using the correct architecture
@@ -52,12 +62,23 @@ Add these lines to /etc/yum.repos.d/couchbase.repo using the correct architectur
52
62
 
53
63
  [couchbase]
54
64
  name = Couchbase package repository
55
- baseurl = http:///packages.couchbase.com/rpm/5.5/x86_64
65
+ baseurl = http://packages.couchbase.com/rpm/5.5/x86_64
56
66
 
57
67
  Then to install libcouchbase itself, run:
58
68
 
59
69
  $ sudo yum update && sudo yum install libcouchbase-devel
60
70
 
71
+ We have preview repositories for RPMs too, use them if you need to try
72
+ fresh version of couchbase gem:
73
+
74
+ [couchbase]
75
+ name = Couchbase package repository
76
+ baseurl = http://packages.couchbase.com/preview/rpm/5.5/i386
77
+
78
+ [couchbase]
79
+ name = Couchbase package repository
80
+ baseurl = http://packages.couchbase.com/preview/rpm/5.5/x86_64
81
+
61
82
  ### Windows
62
83
 
63
84
  There no additional dependencies for Windows systems. The gem carry
@@ -94,6 +115,14 @@ This is equivalent to following forms:
94
115
 
95
116
  The hash parameters take precedence on string URL.
96
117
 
118
+ If you worry about state of your nodes or not sure what node is alive,
119
+ you can pass the list of nodes and the library will iterate over it
120
+ until finds the working one. From that moment it won't use **your**
121
+ list, because node list from cluster config is more actual.
122
+
123
+ c = Couchbase.connect(:bucket => "mybucket",
124
+ :node_list => ['example.com:8091', example.net'])
125
+
97
126
  There is also handy method `Couchbase.bucket` which uses thread local
98
127
  storage to keep the reference to default connection. You can set the
99
128
  connection options via `Couchbase.connection_options`:
@@ -371,6 +400,120 @@ the key and stats as key-value pairs.
371
400
  # ...
372
401
  }
373
402
 
403
+ ### Timers
404
+
405
+ It is possible to create timers to implement general purpose timeouts.
406
+ Note that timers are using microseconds for time intervals. For example,
407
+ following examples increment the keys value five times with 0.5 second
408
+ interval:
409
+
410
+ c.set("foo", 100)
411
+ n = 1
412
+ c.run do
413
+ c.create_periodic_timer(500000) do |tm|
414
+ c.incr("foo") do
415
+ if n == 5
416
+ tm.cancel
417
+ else
418
+ n += 1
419
+ end
420
+ end
421
+ end
422
+ end
423
+
424
+ ### Views (Map/Reduce queries)
425
+
426
+ If you store structured data, they will be treated as documents and you
427
+ can handle them in map/reduce function from Couchbase Views. For example,
428
+ store a couple of posts using memcached API:
429
+
430
+ c['biking'] = {:title => 'Biking',
431
+ :body => 'My biggest hobby is mountainbiking. The other day...',
432
+ :date => '2009/01/30 18:04:11'}
433
+ c['bought-a-cat'] = {:title => 'Bought a Cat',
434
+ :body => 'I went to the the pet store earlier and brought home a little kitty...',
435
+ :date => '2009/01/30 20:04:11'}
436
+ c['hello-world'] = {:title => 'Hello World',
437
+ :body => 'Well hello and welcome to my new blog...',
438
+ :date => '2009/01/15 15:52:20'}
439
+ c.all_docs.count #=> 3
440
+
441
+ Now let's create design doc with sample view and save it in file
442
+ 'blog.json':
443
+
444
+ {
445
+ "_id": "_design/blog",
446
+ "language": "javascript",
447
+ "views": {
448
+ "recent_posts": {
449
+ "map": "function(doc){if(doc.date && doc.title){emit(doc.date, doc.title);}}"
450
+ }
451
+ }
452
+ }
453
+
454
+ This design document could be loaded into the database like this (also you can
455
+ pass the ruby Hash or String with JSON encoded document):
456
+
457
+ c.save_design_doc(File.open('blog.json'))
458
+
459
+ To execute view you need to fetch it from design document `_design/blog`:
460
+
461
+ blog = c.design_docs['blog']
462
+ blog.views #=> ["recent_posts"]
463
+ blog.recent_posts #=> [#<Couchbase::ViewRow:9855800 @id="hello-world" @key="2009/01/15 15:52:20" @value="Hello World" @doc=nil @meta={} @views=[]>, ...]
464
+
465
+ Gem uses streaming parser to access view results so you can iterate them
466
+ easily and if your code won't keep links to the documents GC might free
467
+ them as soon as it decide they are unreachable, because parser doesn't
468
+ store global JSON tree.
469
+
470
+ blog.recent_posts.each do |doc|
471
+ # do something
472
+ # with doc object
473
+ doc.key # gives the key argument of the emit()
474
+ doc.value # gives the value argument of the emit()
475
+ end
476
+
477
+ Load with documents
478
+
479
+ blog.recent_posts(:include_docs => true).each do |doc|
480
+ doc.doc # gives the document which emitted the item
481
+ doc['date'] # gives the argument of the underlying document
482
+ end
483
+
484
+
485
+ You can also use Enumerator to iterate view results
486
+
487
+ require 'date'
488
+ posts_by_date = Hash.new{|h,k| h[k] = []}
489
+ enum = c.all_docs(:include_docs => true).each # request hasn't issued yet
490
+ enum.inject(posts_by_date) do |acc, doc|
491
+ acc[date] = Date.strptime(doc['date'], '%Y/%m/%d')
492
+ acc
493
+ end
494
+
495
+ The Couchbase server could generate errors during view execution with
496
+ `200 OK` and partial results. By default the library raises exception as
497
+ soon as errors detected in the result stream, but you can define the
498
+ callback `on_error` to intercept these errors and do something more
499
+ useful.
500
+
501
+ view = blog.recent_posts(:include_docs => true)
502
+ logger = Logger.new(STDOUT)
503
+
504
+ view.on_error do |from, reason|
505
+ logger.warn("#{view.inspect} received the error '#{reason}' from #{from}")
506
+ end
507
+
508
+ posts = view.each do |doc|
509
+ # do something
510
+ # with doc object
511
+ end
512
+
513
+ Note that errors object in view results usually goes *after* the rows,
514
+ so you will likely receive a number of view results successfully before
515
+ the error is detected.
516
+
374
517
  [1]: http://couchbase.com/issues/browse/RCBC
375
518
  [2]: http://freenode.net/irc_servers.shtml
376
519
  [3]: http://www.couchbase.com/develop/c/current
@@ -35,13 +35,17 @@ Gem::Specification.new do |s|
35
35
  s.extensions = `git ls-files -- ext/**/extconf.rb`.split("\n")
36
36
  s.require_paths = ['lib']
37
37
 
38
- s.add_runtime_dependency 'yajl-ruby', '~> 1.1.0'
38
+ s.add_runtime_dependency 'yaji', '~> 0.3.2'
39
+ s.add_runtime_dependency 'multi_json', '~> 1.0'
39
40
 
40
41
  s.add_development_dependency 'rake', '~> 0.8.7'
41
42
  s.add_development_dependency 'minitest'
42
43
  s.add_development_dependency 'rake-compiler', '>= 0.7.5'
43
44
  s.add_development_dependency 'rdiscount'
44
45
  s.add_development_dependency 'yard'
46
+ s.add_development_dependency 'yard-xml'
45
47
  s.add_development_dependency 'mini_portile'
48
+ s.add_development_dependency 'yajl-ruby', '~> 1.1.0'
46
49
  s.add_development_dependency RUBY_VERSION =~ /^1\.9/ ? 'debugger' : 'ruby-debug'
50
+ s.add_development_dependency 'active_support'
47
51
  end
@@ -0,0 +1,4 @@
1
+ *.log
2
+ *.o
3
+ Makefile
4
+ couchbase_config.h
@@ -0,0 +1,973 @@
1
+ /* vim: ft=c et ts=8 sts=4 sw=4 cino=
2
+ *
3
+ * Copyright 2011, 2012 Couchbase, Inc.
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+
18
+ #include "couchbase_ext.h"
19
+
20
+
21
+ /* TOUCH */
22
+
23
+ static void
24
+ cb_params_touch_alloc(struct params_st *params, lcb_size_t size)
25
+ {
26
+ lcb_size_t ii;
27
+
28
+ params->cmd.touch.num = size;
29
+ params->cmd.touch.items = xcalloc(size, sizeof(lcb_touch_cmd_t));
30
+ if (params->cmd.touch.items == NULL) {
31
+ rb_raise(eClientNoMemoryError, "failed to allocate memory for arguments");
32
+ }
33
+ params->cmd.touch.ptr = xcalloc(size, sizeof(lcb_touch_cmd_t *));
34
+ if (params->cmd.touch.ptr == NULL) {
35
+ rb_raise(eClientNoMemoryError, "failed to allocate memory for arguments");
36
+ }
37
+ for (ii = 0; ii < size; ++ii) {
38
+ params->cmd.touch.ptr[ii] = params->cmd.touch.items + ii;
39
+ }
40
+ }
41
+
42
+ static void
43
+ cb_params_touch_init_item(struct params_st *params, lcb_size_t idx, VALUE key_obj, lcb_time_t exptime)
44
+ {
45
+ key_obj = unify_key(params->bucket, key_obj, 1);
46
+ params->cmd.touch.items[idx].v.v0.key = RSTRING_PTR(key_obj);
47
+ params->cmd.touch.items[idx].v.v0.nkey = RSTRING_LEN(key_obj);
48
+ params->cmd.touch.items[idx].v.v0.exptime = exptime;
49
+ params->npayload += RSTRING_LEN(key_obj) + sizeof(exptime);
50
+ }
51
+
52
+ static int
53
+ cb_params_touch_extract_keys_i(VALUE key, VALUE value, VALUE arg)
54
+ {
55
+ struct params_st *params = (struct params_st *)arg;
56
+ cb_params_touch_init_item(params, params->idx++, key, NUM2ULONG(value));
57
+ return ST_CONTINUE;
58
+ }
59
+
60
+ static void
61
+ cb_params_touch_parse_options(struct params_st *params, VALUE options)
62
+ {
63
+ VALUE tmp;
64
+
65
+ if (NIL_P(options)) {
66
+ return;
67
+ }
68
+ tmp = rb_hash_aref(options, sym_ttl);
69
+ if (tmp != Qnil) {
70
+ params->cmd.touch.ttl = NUM2ULONG(tmp);
71
+ }
72
+ if (RTEST(rb_funcall(options, id_has_key_p, 1, sym_quiet))) {
73
+ params->cmd.touch.quiet = RTEST(rb_hash_aref(options, sym_quiet));
74
+ }
75
+ }
76
+
77
+ static void
78
+ cb_params_touch_parse_arguments(struct params_st *params, int argc, VALUE argv)
79
+ {
80
+ lcb_size_t ii;
81
+
82
+ if (argc < 1) {
83
+ rb_raise(rb_eArgError, "must be at least one key");
84
+ }
85
+ if (argc == 1) {
86
+ VALUE keys = RARRAY_PTR(argv)[0];
87
+ switch(TYPE(keys)) {
88
+ case T_ARRAY:
89
+ /* array of keys as a first argument */
90
+ params->cmd.touch.array = 1;
91
+ cb_params_touch_alloc(params, RARRAY_LEN(keys));
92
+ for (ii = 0; ii < params->cmd.touch.num; ++ii) {
93
+ cb_params_touch_init_item(params, ii, RARRAY_PTR(keys)[ii], params->cmd.touch.ttl);
94
+ }
95
+ break;
96
+ case T_HASH:
97
+ /* key-ttl pairs */
98
+ cb_params_touch_alloc(params, RHASH_SIZE(keys));
99
+ rb_hash_foreach(keys, cb_params_touch_extract_keys_i,
100
+ (VALUE)params);
101
+ break;
102
+ default:
103
+ /* single key */
104
+ cb_params_touch_alloc(params, 1);
105
+ cb_params_touch_init_item(params, 0, keys, params->cmd.touch.ttl);
106
+ }
107
+ } else {
108
+ /* just list of arguments */
109
+ cb_params_touch_alloc(params, argc);
110
+ for (ii = 0; ii < params->cmd.touch.num; ++ii) {
111
+ cb_params_touch_init_item(params, ii, RARRAY_PTR(argv)[ii], params->cmd.touch.ttl);
112
+ }
113
+ }
114
+ }
115
+
116
+
117
+ /* REMOVE */
118
+
119
+ static void
120
+ cb_params_remove_alloc(struct params_st *params, lcb_size_t size)
121
+ {
122
+ lcb_size_t ii;
123
+
124
+ params->cmd.remove.num = size;
125
+ params->cmd.remove.items = xcalloc(size, sizeof(lcb_remove_cmd_t));
126
+ if (params->cmd.remove.items == NULL) {
127
+ rb_raise(eClientNoMemoryError, "failed to allocate memory for arguments");
128
+ }
129
+ params->cmd.remove.ptr = xcalloc(size, sizeof(lcb_remove_cmd_t *));
130
+ if (params->cmd.remove.ptr == NULL) {
131
+ rb_raise(eClientNoMemoryError, "failed to allocate memory for arguments");
132
+ }
133
+ for (ii = 0; ii < size; ++ii) {
134
+ params->cmd.remove.ptr[ii] = params->cmd.remove.items + ii;
135
+ }
136
+ }
137
+
138
+ static void
139
+ cb_params_remove_init_item(struct params_st *params, lcb_size_t idx, VALUE key_obj, lcb_cas_t cas)
140
+ {
141
+ key_obj = unify_key(params->bucket, key_obj, 1);
142
+ params->cmd.remove.items[idx].v.v0.key = RSTRING_PTR(key_obj);
143
+ params->cmd.remove.items[idx].v.v0.nkey = RSTRING_LEN(key_obj);
144
+ params->cmd.remove.items[idx].v.v0.cas = cas;
145
+ params->npayload += RSTRING_LEN(key_obj);
146
+ }
147
+
148
+ static int
149
+ cb_params_remove_extract_keys_i(VALUE key, VALUE value, VALUE arg)
150
+ {
151
+ struct params_st *params = (struct params_st *)arg;
152
+ cb_params_remove_init_item(params, params->idx++, key, NUM2ULL(value));
153
+ return ST_CONTINUE;
154
+ }
155
+
156
+ static void
157
+ cb_params_remove_parse_options(struct params_st *params, VALUE options)
158
+ {
159
+ VALUE tmp;
160
+
161
+ if (NIL_P(options)) {
162
+ return;
163
+ }
164
+ if (RTEST(rb_funcall(options, id_has_key_p, 1, sym_quiet))) {
165
+ params->cmd.remove.quiet = RTEST(rb_hash_aref(options, sym_quiet));
166
+ }
167
+ tmp = rb_hash_aref(options, sym_cas);
168
+ if (tmp != Qnil) {
169
+ params->cmd.remove.cas = NUM2ULL(tmp);
170
+ }
171
+ }
172
+
173
+ static void
174
+ cb_params_remove_parse_arguments(struct params_st *params, int argc, VALUE argv)
175
+ {
176
+ lcb_size_t ii;
177
+
178
+ if (argc < 1) {
179
+ rb_raise(rb_eArgError, "must be at least one key");
180
+ }
181
+ if (argc == 1) {
182
+ VALUE keys = RARRAY_PTR(argv)[0];
183
+ switch(TYPE(keys)) {
184
+ case T_ARRAY:
185
+ /* array of keys as a first argument */
186
+ params->cmd.remove.array = 1;
187
+ cb_params_remove_alloc(params, RARRAY_LEN(keys));
188
+ for (ii = 0; ii < params->cmd.remove.num; ++ii) {
189
+ cb_params_remove_init_item(params, ii, RARRAY_PTR(keys)[ii], params->cmd.remove.cas);
190
+ }
191
+ break;
192
+ case T_HASH:
193
+ /* key-cas pairs */
194
+ cb_params_remove_alloc(params, RHASH_SIZE(keys));
195
+ rb_hash_foreach(keys, cb_params_remove_extract_keys_i,
196
+ (VALUE)params);
197
+ break;
198
+ default:
199
+ /* single key */
200
+ cb_params_remove_alloc(params, 1);
201
+ cb_params_remove_init_item(params, 0, keys, params->cmd.remove.cas);
202
+ }
203
+ } else {
204
+ /* just list of arguments */
205
+ cb_params_remove_alloc(params, argc);
206
+ for (ii = 0; ii < params->cmd.remove.num; ++ii) {
207
+ cb_params_remove_init_item(params, ii, RARRAY_PTR(argv)[ii], params->cmd.remove.cas);
208
+ }
209
+ }
210
+ }
211
+
212
+
213
+ /* STORE */
214
+ static void
215
+ cb_params_store_alloc(struct params_st *params, lcb_size_t size)
216
+ {
217
+ lcb_size_t ii;
218
+
219
+ params->cmd.store.num = size;
220
+ params->cmd.store.items = xcalloc(size, sizeof(lcb_store_cmd_t));
221
+ if (params->cmd.store.items == NULL) {
222
+ rb_raise(eClientNoMemoryError, "failed to allocate memory for arguments");
223
+ }
224
+ params->cmd.store.ptr = xcalloc(size, sizeof(lcb_store_cmd_t *));
225
+ if (params->cmd.store.ptr == NULL) {
226
+ rb_raise(eClientNoMemoryError, "failed to allocate memory for arguments");
227
+ }
228
+ for (ii = 0; ii < size; ++ii) {
229
+ params->cmd.store.ptr[ii] = params->cmd.store.items + ii;
230
+ }
231
+ }
232
+
233
+ static void
234
+ cb_params_store_init_item(struct params_st *params, lcb_size_t idx,
235
+ VALUE key_obj, VALUE value_obj, lcb_uint32_t flags, lcb_cas_t cas,
236
+ lcb_time_t exptime)
237
+ {
238
+ key_obj = unify_key(params->bucket, key_obj, 1);
239
+ value_obj = encode_value(value_obj, params->cmd.store.flags);
240
+ if (value_obj == Qundef) {
241
+ rb_raise(eValueFormatError, "unable to convert value for key '%s'", RSTRING_PTR(key_obj));
242
+ }
243
+ params->cmd.store.items[idx].v.v0.datatype = params->cmd.store.datatype;
244
+ params->cmd.store.items[idx].v.v0.operation = params->cmd.store.operation;
245
+ params->cmd.store.items[idx].v.v0.key = RSTRING_PTR(key_obj);
246
+ params->cmd.store.items[idx].v.v0.nkey = RSTRING_LEN(key_obj);
247
+ params->cmd.store.items[idx].v.v0.bytes = RSTRING_PTR(value_obj);
248
+ params->cmd.store.items[idx].v.v0.nbytes = RSTRING_LEN(value_obj);
249
+ params->cmd.store.items[idx].v.v0.flags = flags;
250
+ params->cmd.store.items[idx].v.v0.cas = cas;
251
+ params->cmd.store.items[idx].v.v0.exptime = exptime;
252
+ params->npayload += RSTRING_LEN(key_obj) + RSTRING_LEN(value_obj) + sizeof(flags) + sizeof(exptime);
253
+ }
254
+
255
+ static int
256
+ cb_params_store_extract_keys_i(VALUE key, VALUE value, VALUE arg)
257
+ {
258
+ struct params_st *params = (struct params_st *)arg;
259
+ cb_params_store_init_item(params, params->idx++, key, value,
260
+ params->cmd.store.flags, 0, params->cmd.store.ttl);
261
+ return ST_CONTINUE;
262
+ }
263
+
264
+ static void
265
+ cb_params_store_parse_options(struct params_st *params, VALUE options)
266
+ {
267
+ VALUE tmp;
268
+
269
+ if (NIL_P(options)) {
270
+ return;
271
+ }
272
+ tmp = rb_hash_aref(options, sym_flags);
273
+ if (tmp != Qnil) {
274
+ params->cmd.store.flags = (lcb_uint32_t)NUM2ULONG(tmp);
275
+ }
276
+ tmp = rb_hash_aref(options, sym_format);
277
+ if (tmp != Qnil) { /* rewrite format bits */
278
+ params->cmd.store.flags = flags_set_format(params->cmd.store.flags, tmp);
279
+ }
280
+ tmp = rb_hash_aref(options, sym_ttl);
281
+ if (tmp != Qnil) {
282
+ params->cmd.store.ttl = NUM2ULONG(tmp);
283
+ }
284
+ tmp = rb_hash_aref(options, sym_cas);
285
+ if (tmp != Qnil) {
286
+ params->cmd.store.cas = NUM2ULL(tmp);
287
+ }
288
+ tmp = rb_hash_aref(options, sym_observe);
289
+ if (tmp != Qnil) {
290
+ Check_Type(tmp, T_HASH);
291
+ rb_funcall(params->bucket->self, id_verify_observe_options, 1, tmp);
292
+ params->cmd.store.observe = tmp;
293
+ }
294
+ if (flags_get_format(params->cmd.store.flags) == sym_document) {
295
+ /* just amend datatype for now */
296
+ params->cmd.store.datatype = 0x01;
297
+ }
298
+ }
299
+
300
+ static void
301
+ cb_params_store_parse_arguments(struct params_st *params, int argc, VALUE argv)
302
+ {
303
+ VALUE keys;
304
+
305
+ if (argc < 1) {
306
+ rb_raise(rb_eArgError, "the key and value must be specified");
307
+ }
308
+ switch (argc) {
309
+ case 1:
310
+ keys = RARRAY_PTR(argv)[0];
311
+ switch(TYPE(keys)) {
312
+ case T_HASH:
313
+ /* key-value pairs */
314
+ cb_params_store_alloc(params, RHASH_SIZE(keys));
315
+ rb_hash_foreach(keys, cb_params_store_extract_keys_i,
316
+ (VALUE)params);
317
+ break;
318
+ default:
319
+ rb_raise(rb_eArgError, "there must be either Hash with key-value pairs"
320
+ " or two separate arguments: key and value");
321
+ }
322
+ break;
323
+ case 2:
324
+ /* just key and value */
325
+ cb_params_store_alloc(params, 1);
326
+ cb_params_store_init_item(params, 0, RARRAY_PTR(argv)[0], RARRAY_PTR(argv)[1],
327
+ params->cmd.store.flags, params->cmd.store.cas, params->cmd.store.ttl);
328
+ break;
329
+ default:
330
+ rb_raise(rb_eArgError, "too many arguments");
331
+ }
332
+ }
333
+
334
+
335
+ /* GET */
336
+ static void
337
+ cb_params_get_alloc(struct params_st *params, lcb_size_t size)
338
+ {
339
+ lcb_size_t ii;
340
+
341
+ params->cmd.get.num = size;
342
+ if (params->cmd.get.replica) {
343
+ params->cmd.get.items = xcalloc(size, sizeof(lcb_get_replica_cmd_t));
344
+ if (params->cmd.get.items_gr == NULL) {
345
+ rb_raise(eClientNoMemoryError, "failed to allocate memory for arguments");
346
+ }
347
+ params->cmd.get.ptr = xcalloc(size, sizeof(lcb_get_replica_cmd_t *));
348
+ if (params->cmd.get.ptr_gr == NULL) {
349
+ rb_raise(eClientNoMemoryError, "failed to allocate memory for arguments");
350
+ }
351
+ for (ii = 0; ii < size; ++ii) {
352
+ params->cmd.get.ptr_gr[ii] = params->cmd.get.items_gr + ii;
353
+ }
354
+ } else {
355
+ params->cmd.get.items = xcalloc(size, sizeof(lcb_get_cmd_t));
356
+ if (params->cmd.get.items == NULL) {
357
+ rb_raise(eClientNoMemoryError, "failed to allocate memory for arguments");
358
+ }
359
+ params->cmd.get.ptr = xcalloc(size, sizeof(lcb_get_cmd_t *));
360
+ if (params->cmd.get.ptr == NULL) {
361
+ rb_raise(eClientNoMemoryError, "failed to allocate memory for arguments");
362
+ }
363
+ for (ii = 0; ii < size; ++ii) {
364
+ params->cmd.get.ptr[ii] = params->cmd.get.items + ii;
365
+ }
366
+ }
367
+ }
368
+
369
+ static void
370
+ cb_params_get_init_item(struct params_st *params, lcb_size_t idx,
371
+ VALUE key_obj, lcb_time_t exptime)
372
+ {
373
+ key_obj = unify_key(params->bucket, key_obj, 1);
374
+ if (params->cmd.get.replica) {
375
+ params->cmd.get.items_gr[idx].v.v0.key = RSTRING_PTR(key_obj);
376
+ params->cmd.get.items_gr[idx].v.v0.nkey = RSTRING_LEN(key_obj);
377
+ } else {
378
+ params->cmd.get.items[idx].v.v0.key = RSTRING_PTR(key_obj);
379
+ params->cmd.get.items[idx].v.v0.nkey = RSTRING_LEN(key_obj);
380
+ params->cmd.get.items[idx].v.v0.exptime = exptime;
381
+ params->cmd.get.items[idx].v.v0.lock = params->cmd.get.lock;
382
+ params->npayload += sizeof(exptime);
383
+ }
384
+ params->npayload += RSTRING_LEN(key_obj);
385
+ }
386
+
387
+ static int
388
+ cb_params_get_extract_keys_i(VALUE key, VALUE value, VALUE arg)
389
+ {
390
+ struct params_st *params = (struct params_st *)arg;
391
+ rb_ary_push(params->cmd.get.keys_ary, key);
392
+ cb_params_get_init_item(params, params->idx++, key, NUM2ULONG(value));
393
+ return ST_CONTINUE;
394
+ }
395
+
396
+ static void
397
+ cb_params_get_parse_options(struct params_st *params, VALUE options)
398
+ {
399
+ VALUE tmp;
400
+
401
+ if (NIL_P(options)) {
402
+ return;
403
+ }
404
+ params->cmd.get.replica = RTEST(rb_hash_aref(options, sym_replica));
405
+ params->cmd.get.extended = RTEST(rb_hash_aref(options, sym_extended));
406
+ params->cmd.get.assemble_hash = RTEST(rb_hash_aref(options, sym_assemble_hash));
407
+ if (RTEST(rb_funcall(options, id_has_key_p, 1, sym_quiet))) {
408
+ params->cmd.get.quiet = RTEST(rb_hash_aref(options, sym_quiet));
409
+ }
410
+ tmp = rb_hash_aref(options, sym_format);
411
+ if (tmp != Qnil) {
412
+ Check_Type(tmp, T_SYMBOL);
413
+ params->cmd.get.forced_format = tmp;
414
+ }
415
+ tmp = rb_hash_aref(options, sym_ttl);
416
+ if (tmp != Qnil) {
417
+ params->cmd.get.ttl = NUM2ULONG(tmp);
418
+ }
419
+ /* boolean or number of seconds to lock */
420
+ tmp = rb_hash_aref(options, sym_lock);
421
+ if (tmp != Qnil) {
422
+ params->cmd.get.lock = RTEST(tmp);
423
+ if (TYPE(tmp) == T_FIXNUM) {
424
+ params->cmd.get.ttl = NUM2ULONG(tmp);
425
+ }
426
+ }
427
+ }
428
+
429
+ static void
430
+ cb_params_get_parse_arguments(struct params_st *params, int argc, VALUE argv)
431
+ {
432
+ lcb_size_t ii;
433
+
434
+ if (argc < 1) {
435
+ rb_raise(rb_eArgError, "must be at least one key");
436
+ }
437
+ if (argc == 1) {
438
+ VALUE keys = RARRAY_PTR(argv)[0];
439
+ switch(TYPE(keys)) {
440
+ case T_ARRAY:
441
+ /* array of keys as a first argument */
442
+ params->cmd.get.array = 1;
443
+ cb_params_get_alloc(params, RARRAY_LEN(keys));
444
+ for (ii = 0; ii < params->cmd.get.num; ++ii) {
445
+ rb_ary_push(params->cmd.get.keys_ary, RARRAY_PTR(keys)[ii]);
446
+ cb_params_get_init_item(params, ii, RARRAY_PTR(keys)[ii], params->cmd.get.ttl);
447
+ }
448
+ break;
449
+ case T_HASH:
450
+ /* key-ttl pairs */
451
+ if (params->cmd.get.replica) {
452
+ rb_raise(rb_eArgError, "must be either list of key or single key");
453
+ }
454
+ params->cmd.get.gat = 1;
455
+ cb_params_get_alloc(params, RHASH_SIZE(keys));
456
+ rb_hash_foreach(keys, cb_params_get_extract_keys_i, (VALUE)params);
457
+ break;
458
+ default:
459
+ /* single key */
460
+ cb_params_get_alloc(params, 1);
461
+ rb_ary_push(params->cmd.get.keys_ary, keys);
462
+ cb_params_get_init_item(params, 0, keys, params->cmd.get.ttl);
463
+ }
464
+ } else {
465
+ /* just list of arguments */
466
+ cb_params_get_alloc(params, argc);
467
+ for (ii = 0; ii < params->cmd.get.num; ++ii) {
468
+ rb_ary_push(params->cmd.get.keys_ary, RARRAY_PTR(argv)[ii]);
469
+ cb_params_get_init_item(params, ii, RARRAY_PTR(argv)[ii], params->cmd.get.ttl);
470
+ }
471
+ }
472
+ }
473
+
474
+
475
+ /* ARITH */
476
+ static void
477
+ cb_params_arith_alloc(struct params_st *params, lcb_size_t size)
478
+ {
479
+ lcb_size_t ii;
480
+
481
+ params->cmd.arith.num = size;
482
+ params->cmd.arith.items = xcalloc(size, sizeof(lcb_arithmetic_cmd_t));
483
+ if (params->cmd.arith.items == NULL) {
484
+ rb_raise(eClientNoMemoryError, "failed to allocate memory for arguments");
485
+ }
486
+ params->cmd.arith.ptr = xcalloc(size, sizeof(lcb_arithmetic_cmd_t *));
487
+ if (params->cmd.arith.ptr == NULL) {
488
+ rb_raise(eClientNoMemoryError, "failed to allocate memory for arguments");
489
+ }
490
+ for (ii = 0; ii < size; ++ii) {
491
+ params->cmd.arith.ptr[ii] = params->cmd.arith.items + ii;
492
+ }
493
+ }
494
+
495
+ static void
496
+ cb_params_arith_init_item(struct params_st *params, lcb_size_t idx,
497
+ VALUE key_obj, lcb_int64_t delta)
498
+ {
499
+ key_obj = unify_key(params->bucket, key_obj, 1);
500
+ params->cmd.arith.items[idx].v.v0.key = RSTRING_PTR(key_obj);
501
+ params->cmd.arith.items[idx].v.v0.nkey = RSTRING_LEN(key_obj);
502
+ params->cmd.arith.items[idx].v.v0.delta = delta * params->cmd.arith.sign;
503
+ params->cmd.arith.items[idx].v.v0.exptime = params->cmd.arith.ttl;
504
+ params->cmd.arith.items[idx].v.v0.create = params->cmd.arith.create;
505
+ params->cmd.arith.items[idx].v.v0.initial = params->cmd.arith.initial;
506
+ params->npayload += RSTRING_LEN(key_obj);
507
+ }
508
+
509
+ static int
510
+ cb_params_arith_extract_keys_i(VALUE key, VALUE value, VALUE arg)
511
+ {
512
+ struct params_st *params = (struct params_st *)arg;
513
+ cb_params_arith_init_item(params, params->idx++, key, NUM2ULONG(value) & INT64_MAX);
514
+ return ST_CONTINUE;
515
+ }
516
+
517
+ static void
518
+ cb_params_arith_parse_options(struct params_st *params, VALUE options)
519
+ {
520
+ VALUE tmp;
521
+
522
+ if (NIL_P(options)) {
523
+ return;
524
+ }
525
+ params->cmd.arith.create = RTEST(rb_hash_aref(options, sym_create));
526
+ params->cmd.arith.extended = RTEST(rb_hash_aref(options, sym_extended));
527
+ tmp = rb_hash_aref(options, sym_ttl);
528
+ if (tmp != Qnil) {
529
+ params->cmd.arith.ttl = NUM2ULONG(tmp);
530
+ }
531
+ tmp = rb_hash_aref(options, sym_initial);
532
+ if (tmp != Qnil) {
533
+ params->cmd.arith.initial = NUM2ULL(tmp);
534
+ params->cmd.arith.create = 1;
535
+ }
536
+ tmp = rb_hash_aref(options, sym_delta);
537
+ if (tmp != Qnil) {
538
+ params->cmd.arith.delta = NUM2ULL(tmp) & INT64_MAX;
539
+ }
540
+ tmp = rb_hash_aref(options, sym_format);
541
+ if (tmp != Qnil) { /* rewrite format bits */
542
+ params->cmd.arith.format = tmp;
543
+ }
544
+ if (params->cmd.arith.format == sym_document) {
545
+ /* just amend datatype for now */
546
+ params->cmd.arith.datatype = 0x01;
547
+ }
548
+ }
549
+
550
+ static void
551
+ cb_params_arith_parse_arguments(struct params_st *params, int argc, VALUE argv)
552
+ {
553
+ lcb_size_t ii;
554
+
555
+ if (argc < 1) {
556
+ rb_raise(rb_eArgError, "must be at least one key");
557
+ }
558
+ if (argc == 1) {
559
+ VALUE keys = RARRAY_PTR(argv)[0];
560
+ switch(TYPE(keys)) {
561
+ case T_ARRAY:
562
+ /* array of keys as a first argument */
563
+ params->cmd.arith.array = 1;
564
+ cb_params_arith_alloc(params, RARRAY_LEN(keys));
565
+ for (ii = 0; ii < params->cmd.arith.num; ++ii) {
566
+ cb_params_arith_init_item(params, ii, RARRAY_PTR(keys)[ii], params->cmd.arith.delta);
567
+ }
568
+ break;
569
+ case T_HASH:
570
+ /* key-delta pairs */
571
+ cb_params_arith_alloc(params, RHASH_SIZE(keys));
572
+ rb_hash_foreach(keys, cb_params_arith_extract_keys_i, (VALUE)params);
573
+ break;
574
+ default:
575
+ /* single key */
576
+ cb_params_arith_alloc(params, 1);
577
+ cb_params_arith_init_item(params, 0, keys, params->cmd.arith.delta);
578
+ }
579
+ } else {
580
+ /* just list of arguments */
581
+ cb_params_arith_alloc(params, argc);
582
+ for (ii = 0; ii < params->cmd.arith.num; ++ii) {
583
+ cb_params_arith_init_item(params, ii, RARRAY_PTR(argv)[ii], params->cmd.arith.delta);
584
+ }
585
+ }
586
+ }
587
+
588
+
589
+ /* STATS */
590
+ static void
591
+ cb_params_stats_alloc(struct params_st *params, lcb_size_t size)
592
+ {
593
+ lcb_size_t ii;
594
+
595
+ params->cmd.stats.num = size;
596
+ params->cmd.stats.items = xcalloc(size, sizeof(lcb_server_stats_cmd_t));
597
+ if (params->cmd.stats.items == NULL) {
598
+ rb_raise(eClientNoMemoryError, "failed to allocate memory for arguments");
599
+ }
600
+ params->cmd.stats.ptr = xcalloc(size, sizeof(lcb_server_stats_cmd_t *));
601
+ if (params->cmd.stats.ptr == NULL) {
602
+ rb_raise(eClientNoMemoryError, "failed to allocate memory for arguments");
603
+ }
604
+ for (ii = 0; ii < size; ++ii) {
605
+ params->cmd.stats.ptr[ii] = params->cmd.stats.items + ii;
606
+ }
607
+ }
608
+
609
+ static void
610
+ cb_params_stats_init_item(struct params_st *params, lcb_size_t idx,
611
+ VALUE key_obj)
612
+ {
613
+ key_obj = unify_key(params->bucket, key_obj, 1);
614
+ params->cmd.stats.items[idx].v.v0.name = RSTRING_PTR(key_obj);
615
+ params->cmd.stats.items[idx].v.v0.nname = RSTRING_LEN(key_obj);
616
+ params->npayload += RSTRING_LEN(key_obj);
617
+ }
618
+
619
+ static void
620
+ cb_params_stats_parse_arguments(struct params_st *params, int argc, VALUE argv)
621
+ {
622
+ lcb_size_t ii;
623
+
624
+ if (argc == 1) {
625
+ VALUE keys = RARRAY_PTR(argv)[0];
626
+ switch(TYPE(keys)) {
627
+ case T_ARRAY:
628
+ /* array of keys as a first argument */
629
+ params->cmd.stats.array = 1;
630
+ cb_params_stats_alloc(params, RARRAY_LEN(keys));
631
+ for (ii = 0; ii < params->cmd.stats.num; ++ii) {
632
+ cb_params_stats_init_item(params, ii, RARRAY_PTR(keys)[ii]);
633
+ }
634
+ break;
635
+ default:
636
+ /* single key */
637
+ cb_params_stats_alloc(params, 1);
638
+ cb_params_stats_init_item(params, 0, keys);
639
+ }
640
+ } else if (argc == 0) {
641
+ /* stat without argument (single empty struct) */
642
+ cb_params_stats_alloc(params, 1);
643
+ } else {
644
+ /* just list of arguments */
645
+ cb_params_stats_alloc(params, argc);
646
+ for (ii = 0; ii < params->cmd.stats.num; ++ii) {
647
+ cb_params_stats_init_item(params, ii, RARRAY_PTR(argv)[ii]);
648
+ }
649
+ }
650
+ }
651
+
652
+
653
+ /* REMOVE */
654
+
655
+ static void
656
+ cb_params_observe_alloc(struct params_st *params, lcb_size_t size)
657
+ {
658
+ lcb_size_t ii;
659
+
660
+ params->cmd.observe.num = size;
661
+ params->cmd.observe.items = xcalloc(size, sizeof(lcb_observe_cmd_t));
662
+ if (params->cmd.observe.items == NULL) {
663
+ rb_raise(eClientNoMemoryError, "failed to allocate memory for arguments");
664
+ }
665
+ params->cmd.observe.ptr = xcalloc(size, sizeof(lcb_observe_cmd_t *));
666
+ if (params->cmd.observe.ptr == NULL) {
667
+ rb_raise(eClientNoMemoryError, "failed to allocate memory for arguments");
668
+ }
669
+ for (ii = 0; ii < size; ++ii) {
670
+ params->cmd.observe.ptr[ii] = params->cmd.observe.items + ii;
671
+ }
672
+ }
673
+
674
+ static void
675
+ cb_params_observe_init_item(struct params_st *params, lcb_size_t idx, VALUE key_obj)
676
+ {
677
+ key_obj = unify_key(params->bucket, key_obj, 1);
678
+ params->cmd.observe.items[idx].v.v0.key = RSTRING_PTR(key_obj);
679
+ params->cmd.observe.items[idx].v.v0.nkey = RSTRING_LEN(key_obj);
680
+ params->npayload += RSTRING_LEN(key_obj);
681
+ }
682
+
683
+ static void
684
+ cb_params_observe_parse_arguments(struct params_st *params, int argc, VALUE argv)
685
+ {
686
+ lcb_size_t ii;
687
+
688
+ if (argc < 1) {
689
+ rb_raise(rb_eArgError, "must be at least one key");
690
+ }
691
+ if (argc == 1) {
692
+ VALUE keys = RARRAY_PTR(argv)[0];
693
+ switch(TYPE(keys)) {
694
+ case T_ARRAY:
695
+ /* array of keys as a first argument */
696
+ params->cmd.observe.array = 1;
697
+ cb_params_observe_alloc(params, RARRAY_LEN(keys));
698
+ for (ii = 0; ii < params->cmd.observe.num; ++ii) {
699
+ cb_params_observe_init_item(params, ii, RARRAY_PTR(keys)[ii]);
700
+ }
701
+ break;
702
+ default:
703
+ /* single key */
704
+ cb_params_observe_alloc(params, 1);
705
+ cb_params_observe_init_item(params, 0, keys);
706
+ }
707
+ } else {
708
+ /* just list of arguments */
709
+ cb_params_observe_alloc(params, argc);
710
+ for (ii = 0; ii < params->cmd.observe.num; ++ii) {
711
+ cb_params_observe_init_item(params, ii, RARRAY_PTR(argv)[ii]);
712
+ }
713
+ }
714
+ }
715
+
716
+
717
+ /* UNLOCK */
718
+ static void
719
+ cb_params_unlock_alloc(struct params_st *params, lcb_size_t size)
720
+ {
721
+ lcb_size_t ii;
722
+
723
+ params->cmd.unlock.num = size;
724
+ params->cmd.unlock.items = xcalloc(size, sizeof(lcb_unlock_cmd_t));
725
+ if (params->cmd.unlock.items == NULL) {
726
+ rb_raise(eClientNoMemoryError, "failed to allocate memory for arguments");
727
+ }
728
+ params->cmd.unlock.ptr = xcalloc(size, sizeof(lcb_unlock_cmd_t *));
729
+ if (params->cmd.unlock.ptr == NULL) {
730
+ rb_raise(eClientNoMemoryError, "failed to allocate memory for arguments");
731
+ }
732
+ for (ii = 0; ii < size; ++ii) {
733
+ params->cmd.unlock.ptr[ii] = params->cmd.unlock.items + ii;
734
+ }
735
+ }
736
+
737
+ static void
738
+ cb_params_unlock_init_item(struct params_st *params, lcb_size_t idx, VALUE key_obj, lcb_cas_t cas)
739
+ {
740
+ key_obj = unify_key(params->bucket, key_obj, 1);
741
+ params->cmd.unlock.items[idx].v.v0.key = RSTRING_PTR(key_obj);
742
+ params->cmd.unlock.items[idx].v.v0.nkey = RSTRING_LEN(key_obj);
743
+ params->cmd.unlock.items[idx].v.v0.cas = cas;
744
+ params->npayload += RSTRING_LEN(key_obj);
745
+ }
746
+
747
+ static int
748
+ cb_params_unlock_extract_keys_i(VALUE key, VALUE value, VALUE arg)
749
+ {
750
+ struct params_st *params = (struct params_st *)arg;
751
+ cb_params_unlock_init_item(params, params->idx++, key, NUM2ULL(value));
752
+ return ST_CONTINUE;
753
+ }
754
+
755
+ static void
756
+ cb_params_unlock_parse_options(struct params_st *params, VALUE options)
757
+ {
758
+ VALUE tmp;
759
+
760
+ if (NIL_P(options)) {
761
+ return;
762
+ }
763
+ tmp = rb_hash_aref(options, sym_cas);
764
+ if (tmp != Qnil) {
765
+ params->cmd.unlock.cas = NUM2ULL(tmp);
766
+ }
767
+ if (RTEST(rb_funcall(options, id_has_key_p, 1, sym_quiet))) {
768
+ params->cmd.unlock.quiet = RTEST(rb_hash_aref(options, sym_quiet));
769
+ }
770
+ }
771
+
772
+ static void
773
+ cb_params_unlock_parse_arguments(struct params_st *params, int argc, VALUE argv)
774
+ {
775
+ if (argc == 1) {
776
+ VALUE keys = RARRAY_PTR(argv)[0];
777
+ switch(TYPE(keys)) {
778
+ case T_HASH:
779
+ /* key-cas pairs */
780
+ cb_params_unlock_alloc(params, RHASH_SIZE(keys));
781
+ rb_hash_foreach(keys, cb_params_unlock_extract_keys_i, (VALUE)params);
782
+ break;
783
+ default:
784
+ /* single key */
785
+ cb_params_unlock_alloc(params, 1);
786
+ cb_params_unlock_init_item(params, 0, keys, params->cmd.unlock.cas);
787
+ }
788
+ } else {
789
+ rb_raise(rb_eArgError, "must be either Hash or single key with cas option");
790
+ }
791
+ }
792
+
793
+
794
+ /* VERSION */
795
+ static void
796
+ cb_params_version_alloc(struct params_st *params)
797
+ {
798
+ params->cmd.version.num = 1;
799
+ params->cmd.version.items = xcalloc(1, sizeof(lcb_server_version_cmd_t));
800
+ if (params->cmd.version.items == NULL) {
801
+ rb_raise(eClientNoMemoryError, "failed to allocate memory for arguments");
802
+ }
803
+ params->cmd.version.ptr = xcalloc(1, sizeof(lcb_server_version_cmd_t *));
804
+ if (params->cmd.version.ptr == NULL) {
805
+ rb_raise(eClientNoMemoryError, "failed to allocate memory for arguments");
806
+ }
807
+ params->cmd.version.ptr[0] = params->cmd.version.items;
808
+ }
809
+
810
+
811
+ /* common stuff */
812
+ void
813
+ cb_params_destroy(struct params_st *params)
814
+ {
815
+ #define _release_data_for(type) \
816
+ xfree(params->cmd.type.items); \
817
+ xfree(params->cmd.type.ptr);
818
+
819
+ switch (params->type) {
820
+ case cmd_get:
821
+ _release_data_for(get);
822
+ xfree(params->cmd.get.items_gr);
823
+ xfree(params->cmd.get.ptr_gr);
824
+ break;
825
+ case cmd_touch:
826
+ _release_data_for(touch);
827
+ break;
828
+ case cmd_arith:
829
+ _release_data_for(arith);
830
+ break;
831
+ case cmd_remove:
832
+ _release_data_for(remove);
833
+ break;
834
+ case cmd_store:
835
+ _release_data_for(store);
836
+ break;
837
+ case cmd_stats:
838
+ _release_data_for(stats);
839
+ break;
840
+ case cmd_version:
841
+ _release_data_for(version);
842
+ break;
843
+ case cmd_observe:
844
+ _release_data_for(observe);
845
+ break;
846
+ case cmd_unlock:
847
+ _release_data_for(unlock);
848
+ break;
849
+ }
850
+ #undef _release_data_for
851
+ }
852
+
853
+ struct build_params_st
854
+ {
855
+ struct params_st *params;
856
+ int argc;
857
+ VALUE argv;
858
+ };
859
+
860
+ static VALUE
861
+ do_params_build(VALUE ptr)
862
+ {
863
+ VALUE opts;
864
+ /* unpack arguments */
865
+ struct build_params_st *p = (struct build_params_st *)ptr;
866
+ struct params_st *params = p->params;
867
+ int argc = p->argc;
868
+ VALUE argv = p->argv;
869
+
870
+ /* extract options */
871
+ if (argc > 1 && TYPE(RARRAY_PTR(argv)[argc-1]) == T_HASH) {
872
+ opts = rb_ary_pop(argv);
873
+ --argc;
874
+ } else {
875
+ opts = Qnil;
876
+ }
877
+
878
+ params->npayload = PACKET_HEADER_SIZE; /* size of packet header */
879
+ switch (params->type) {
880
+ case cmd_touch:
881
+ params->cmd.touch.quiet = params->bucket->quiet;
882
+ params->cmd.touch.ttl = params->bucket->default_ttl;
883
+ cb_params_touch_parse_options(params, opts);
884
+ cb_params_touch_parse_arguments(params, argc, argv);
885
+ break;
886
+ case cmd_remove:
887
+ params->cmd.remove.quiet = params->bucket->quiet;
888
+ if (argc == 2) {
889
+ int type = TYPE(RARRAY_PTR(argv)[1]);
890
+ if (type == T_FIXNUM || type == T_BIGNUM) {
891
+ /* allow form delete("foo", 0xdeadbeef) */
892
+ --argc;
893
+ params->cmd.remove.cas = NUM2ULL(rb_ary_pop(argv));
894
+ }
895
+ }
896
+ cb_params_remove_parse_options(params, opts);
897
+ cb_params_remove_parse_arguments(params, argc, argv);
898
+ break;
899
+ case cmd_store:
900
+ if (argc == 1 && opts != Qnil) {
901
+ /* put last hash back because it is the value */
902
+ rb_ary_push(argv, opts);
903
+ opts = Qnil;
904
+ ++argc;
905
+ }
906
+ params->cmd.store.datatype = 0x00;
907
+ params->cmd.store.ttl = params->bucket->default_ttl;
908
+ params->cmd.store.flags = flags_set_format(params->bucket->default_flags,
909
+ params->bucket->default_format);
910
+ params->cmd.store.observe = Qnil;
911
+ cb_params_store_parse_options(params, opts);
912
+ cb_params_store_parse_arguments(params, argc, argv);
913
+ break;
914
+ case cmd_get:
915
+ params->cmd.get.quiet = params->bucket->quiet;
916
+ cb_params_get_parse_options(params, opts);
917
+ cb_params_get_parse_arguments(params, argc, argv);
918
+ break;
919
+ case cmd_arith:
920
+ params->cmd.arith.delta = 1;
921
+ params->cmd.arith.format = params->bucket->default_format;
922
+ params->cmd.arith.ttl = params->bucket->default_ttl;
923
+ if (argc == 2 && TYPE(RARRAY_PTR(argv)[1]) == T_FIXNUM) {
924
+ /* allow form incr("foo", 1) */
925
+ --argc;
926
+ params->cmd.arith.delta = NUM2ULL(rb_ary_pop(argv)) & INT64_MAX;
927
+ }
928
+ cb_params_arith_parse_options(params, opts);
929
+ cb_params_arith_parse_arguments(params, argc, argv);
930
+ break;
931
+ case cmd_stats:
932
+ cb_params_stats_parse_arguments(params, argc, argv);
933
+ break;
934
+ case cmd_version:
935
+ cb_params_version_alloc(params);
936
+ break;
937
+ case cmd_observe:
938
+ cb_params_observe_parse_arguments(params, argc, argv);
939
+ break;
940
+ case cmd_unlock:
941
+ params->cmd.unlock.quiet = params->bucket->quiet;
942
+ if (argc == 2) {
943
+ int type = TYPE(RARRAY_PTR(argv)[1]);
944
+ if (type == T_FIXNUM || type == T_BIGNUM) {
945
+ /* allow form unlock("foo", 0xdeadbeef) */
946
+ --argc;
947
+ params->cmd.unlock.cas = NUM2ULL(rb_ary_pop(argv));
948
+ }
949
+ }
950
+ cb_params_unlock_parse_options(params, opts);
951
+ cb_params_unlock_parse_arguments(params, argc, argv);
952
+ break;
953
+ }
954
+
955
+ return Qnil;
956
+ }
957
+
958
+ void
959
+ cb_params_build(struct params_st *params, int argc, VALUE argv)
960
+ {
961
+ int fail = 0;
962
+ struct build_params_st args;
963
+
964
+ args.params = params;
965
+ args.argc = argc;
966
+ args.argv = argv;
967
+ rb_protect(do_params_build, (VALUE)&args, &fail);
968
+ if (fail) {
969
+ cb_params_destroy(params);
970
+ /* raise exception from protected block */
971
+ rb_jump_tag(fail);
972
+ }
973
+ }