couchbase 0.9.8 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. data/.gitignore +8 -0
  2. data/.yardopts +5 -0
  3. data/HISTORY.markdown +14 -10
  4. data/README.markdown +293 -98
  5. data/Rakefile +19 -24
  6. data/couchbase.gemspec +25 -7
  7. data/ext/couchbase_ext/couchbase_ext.c +2332 -0
  8. data/ext/couchbase_ext/extconf.rb +102 -0
  9. data/lib/couchbase.rb +20 -30
  10. data/lib/couchbase/bucket.rb +43 -112
  11. data/lib/couchbase/version.rb +3 -2
  12. data/tasks/benchmark.rake +6 -0
  13. data/tasks/compile.rake +52 -0
  14. data/tasks/doc.rake +27 -0
  15. data/tasks/test.rake +94 -0
  16. data/tasks/util.rake +21 -0
  17. data/test/profile/.gitignore +1 -0
  18. data/test/profile/Gemfile +6 -0
  19. data/test/profile/benchmark.rb +195 -0
  20. data/test/setup.rb +107 -18
  21. data/test/test_arithmetic.rb +98 -0
  22. data/test/test_async.rb +211 -0
  23. data/test/test_bucket.rb +126 -23
  24. data/test/test_cas.rb +59 -0
  25. data/test/test_couchbase.rb +22 -3
  26. data/test/test_delete.rb +63 -0
  27. data/test/test_errors.rb +82 -0
  28. data/test/test_flush.rb +49 -0
  29. data/test/test_format.rb +98 -0
  30. data/test/test_get.rb +236 -0
  31. data/test/test_stats.rb +53 -0
  32. data/test/test_store.rb +186 -0
  33. data/test/test_touch.rb +57 -0
  34. data/test/test_version.rb +17 -0
  35. metadata +72 -58
  36. data/lib/couchbase/couchdb.rb +0 -107
  37. data/lib/couchbase/document.rb +0 -71
  38. data/lib/couchbase/http_status.rb +0 -118
  39. data/lib/couchbase/latch.rb +0 -71
  40. data/lib/couchbase/memcached.rb +0 -372
  41. data/lib/couchbase/node.rb +0 -49
  42. data/lib/couchbase/rest_client.rb +0 -124
  43. data/lib/couchbase/view.rb +0 -182
  44. data/test/support/buckets-config.json +0 -843
  45. data/test/support/sample_design_doc.json +0 -9
  46. data/test/test_couchdb.rb +0 -98
  47. data/test/test_document.rb +0 -11
  48. data/test/test_latch.rb +0 -88
  49. data/test/test_memcached.rb +0 -59
  50. data/test/test_rest_client.rb +0 -14
  51. data/test/test_view.rb +0 -98
data/.gitignore CHANGED
@@ -1,4 +1,12 @@
1
1
  *.gem
2
+ *.so
2
3
  .bundle
4
+ .yardoc
3
5
  Gemfile.lock
6
+ core*
7
+ doc/
8
+ ext/couchbase/Makefile
9
+ ext/couchbase/mkmf.log
4
10
  pkg/*
11
+ test/CouchbaseMock.jar
12
+ tmp
@@ -0,0 +1,5 @@
1
+ --protected
2
+ --no-private
3
+ -
4
+ README.markdown
5
+ HISTORY.markdown
@@ -1,15 +1,20 @@
1
- === 0.9.8 / 2011-12-16
1
+ ## 1.0.0 / 2011-12-23
2
+
3
+ * Implement all operations using libcouchbase as backend
4
+ * Remove views code. It will be re-added in 1.1 version
5
+
6
+ ## 0.9.8 / 2011-12-16
2
7
 
3
8
  * Fix RCBC-10: It was impossible to store data in non-default buckets
4
9
 
5
- === 0.9.7 / 2011-10-04
10
+ ## 0.9.7 / 2011-10-04
6
11
 
7
12
  * Fix design doc removing
8
13
  * Fix 'set' method signature: add missing options argument
9
14
  * Rename gem to 'couchbase' for easy of use. The github project still
10
15
  is 'couchbase-ruby-client'
11
16
 
12
- === 0.9.6 / 2011-10-04
17
+ ## 0.9.6 / 2011-10-04
13
18
 
14
19
  * Fix bug with decoding multiget result
15
20
  * Allow create design documents from IO and String
@@ -18,11 +23,11 @@
18
23
  * Remove dependency on libyajl library: it bundled with yaji now
19
24
  * Update rake tasks: create zip- and tar-balls
20
25
 
21
- === 0.9.5 / 2011-08-24
26
+ ## 0.9.5 / 2011-08-24
22
27
 
23
28
  * Update installation notes in README
24
29
 
25
- === 0.9.4 / 2011-08-24
30
+ ## 0.9.4 / 2011-08-24
26
31
 
27
32
  * Use streaming json parser to iterate over view results
28
33
  * Update memcached gem dependency to v1.3
@@ -32,23 +37,22 @@
32
37
  * Fix bug with unicode parsing in config listener
33
38
  * Add more unit tests
34
39
 
35
- === 0.9.3 / 2011-07-29
40
+ ## 0.9.3 / 2011-07-29
36
41
 
37
42
  * Use Latch (via Mutex and ConditionVariable) to wait until initial
38
43
  setup will be finished.
39
44
  * Update prefix for development views (from '$dev_' to 'dev_')
40
45
 
41
- === 0.9.2 / 2011-07-29
46
+ ## 0.9.2 / 2011-07-29
42
47
 
43
48
  * Use zero TTL by default to store records forever
44
49
  * Update documentation
45
50
  * Sleep if setup isn't done
46
51
 
47
-
48
- === 0.9.1 / 2011-07-25
52
+ ## 0.9.1 / 2011-07-25
49
53
 
50
54
  * Minor bugfix for RestClient initialization
51
55
 
52
- === 0.9.0 / 2011-07-25
56
+ ## 0.9.0 / 2011-07-25
53
57
 
54
58
  * Initial public release
@@ -1,135 +1,330 @@
1
- Couchbase Ruby Client
2
- =====================
1
+ # Couchbase Ruby Client
3
2
 
4
3
  This is the official client library for use with Couchbase Server.
5
4
 
6
- INSTALL
7
- =======
5
+ ## INSTALL
8
6
 
9
7
  This gem depends on a couple of external libraries to work with JSON and
10
- HTTP. For JSON it uses [yajl-ruby][1] and [yaji][2] which are built atop
11
- of [yajl][3]. For HTTP iteraction it uses [curb][4], the curl bindings for
12
- ruby. To install it use your package manager. On debian you can install
13
- it, use `aptitude`:
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:
14
12
 
15
- $ sudo aptitude install libcurl3-dev
13
+ $ gem install yajl-ruby
16
14
 
17
- After that you can install the gem via rubygems:
15
+ In most cases installing libcouchbase is just as simple.
16
+
17
+ ### MacOS (Homebrew)
18
+
19
+ $ brew install libcouchbase
20
+
21
+ Or if our pull request isn't yet merged:
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
25
+
26
+ ### Debian (Ubuntu)
27
+
28
+ Download packages depending on your architecture:
29
+
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
32
+
33
+ or
34
+
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
+
38
+ Then install them using dpkg tool
39
+
40
+ $ sudo dpkg -i lib{vbucket,couchbase}*.deb
41
+
42
+ ### Centos (Redhat and rpm-based systems)
43
+
44
+ Download packages depending on your architecture:
45
+
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
48
+
49
+ or
50
+
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
53
+
54
+ Then install them using rpm tool
55
+
56
+ $ sudo rpm -ivh lib{vbucket,couchbase}*.rpm
57
+
58
+ ### Couchbase gem
59
+
60
+ Now install the couchbase gem itself
18
61
 
19
62
  $ gem install couchbase
20
63
 
21
- USAGE
22
- =====
64
+
65
+ ## USAGE
23
66
 
24
67
  First of all you need to load library:
25
68
 
26
69
  require 'couchbase'
27
70
 
28
- To establish connection with couchbase you need to specify at least pool
29
- URI. Also you can choose custom bucket name and memcached SASL
30
- authentication and Couchbase REST API.
71
+ There are several ways to establish new connection to Couchbase Server.
72
+ By default it uses the `http://localhost:8091/pools/default/buckets/default`
73
+ as the endpoint. The client will automatically adjust configuration when
74
+ the cluster will rebalance its nodes when nodes are added or deleted
75
+ therefore this client is "smart".
76
+
77
+ c = Couchbase.new
31
78
 
79
+ This is equivalent to following forms:
80
+
81
+ c = Couchbase.new("http://localhost:8091/pools/default/buckets/default")
32
82
  c = Couchbase.new("http://localhost:8091/pools/default")
33
- c = Couchbase.new("http://localhost:8091/pools/default", :bucket_name => 'blog')
34
- c = Couchbase.new("http://localhost:8091/pools/default",
35
- :bucket_name => 'blog', :bucket_password => 'secret')
36
-
37
- This gem supports memcached API accessible for [memcached][5] client
38
- with a bit syntax sugar:
39
-
40
- c.set('password', 'secret')
41
- c.get('password') #=> "secret"
42
- c['password'] = 'secret'
43
- c['password'] #=> "secret"
44
- c['counter'] = 1 #=> 1
45
- c['counter'] += 1 #=> 2
46
- c['counter'] #=> 2
47
- c.increment('counter', 10)
48
- c.flush
83
+ c = Couchbase.new("http://localhost:8091")
84
+ c = Couchbase.new(:host => "localhost")
85
+ c = Couchbase.new(:host => "localhost", :port => 8091)
86
+ c = Couchbase.new(:pool => "default", :bucket => "default")
49
87
 
50
- But if you store structured data, they will be treated as documents and
51
- you can handle them in map/reduce function from CouchDB views. For
52
- example, store a couple of posts using memcached API:
88
+ The hash parameters take precedence on string URL.
53
89
 
54
- c['biking'] = {:title => 'Biking',
55
- :body => 'I went to the the pet store earlier and brought home a little kitty...',
56
- :date => '2009/01/30 18:04:11'}
57
- c['bought-a-cat'] = {:title => 'Biking',
58
- :body => 'My biggest hobby is mountainbiking. The other day...',
59
- :date => '2009/01/30 18:04:11'}
60
- c['hello-world'] = {:title => 'Hello World',
61
- :body => 'Well hello and welcome to my new blog...',
62
- :date => '2009/01/15 15:52:20'}
63
- c.all_docs.count #=> 3
90
+ The library supports both synchronous and asynchronous mode. You can
91
+ choose either using the `:async` option or attribute.
64
92
 
65
- Now let's create design doc with sample view and save it in file
66
- 'blog.json':
93
+ c = Couchbase.new(:async => true)
94
+ # ... asynchronous mode
95
+ c.async = false
96
+ # ... synchronous mode
67
97
 
68
- {
69
- "_id": "_design/blog",
70
- "language": "javascript",
71
- "views": {
72
- "recent_posts": {
73
- "map": "function(doc){if(doc.date && doc.title){emit(doc.date, doc.title);}}"
74
- }
75
- }
76
- }
98
+ In asynchronous mode all operations will return control to caller
99
+ without blocking current thread. You can pass the block to method and it
100
+ will be called with result when the operation will be completed. You
101
+ need to run event loop when you scheduled your operations:
77
102
 
78
- This design document could be loaded into the database like this (also you can
79
- pass the ruby Hash or String with JSON encoded document):
103
+ c = Couchbase.new(:async => true)
104
+ c.run do |conn|
105
+ conn.get("foo") {|ret| puts ret.value}
106
+ conn.set("bar", "baz")
107
+ end
80
108
 
81
- c.save_design_doc(File.open('blog.json'))
109
+ The handlers could be nested
82
110
 
83
- To execute view you need to fetch it from design document `_design/blog`:
111
+ c.run do |conn|
112
+ conn.get("foo") do |ret|
113
+ conn.incr(ret.value, :initial => 0)
114
+ end
115
+ end
84
116
 
85
- blog = c.design_docs['blog']
86
- blog.views #=> ["recent_posts"]
87
- blog.recent_posts #=> [#<Couchbase::Document:14244860 {"id"=>"hello-world", "key"=>"2009/01/15 15:52:20", "value"=>"Hello World"}>,...]
117
+ The asynchronous callback receives instance of `Couchbase::Result` which
118
+ responds to several methods to figure out what was happened:
88
119
 
89
- Gem uses streaming parser to access view results so you can iterate them
90
- easily and if your code won't keep links to the documents GC might free
91
- them as soon as it decide they are unreachable, because parser doesn't
92
- store global JSON tree.
120
+ * `success?`. Returns `true` if operation succed.
93
121
 
94
- posts = blog.recent_posts.each do |doc|
95
- # do something
96
- # with doc object
97
- end
122
+ * `error`. Returns `nil` or exception object (subclass of
123
+ `Couchbase::Error::Base`) if something went wrong.
98
124
 
99
- You can also use Enumerator to iterate view results
125
+ * `key`
100
126
 
101
- require 'date'
102
- posts_by_date = Hash.new{|h,k| h[k] = []}
103
- enum = c.all_docs.each # request hasn't issued yet
104
- enum.inject(posts_by_date) do |acc, doc|
105
- acc[date] = Date.strptime(doc['date'], '%Y/%m/%d')
106
- acc
107
- end
127
+ * `value`
108
128
 
109
- The Couchbase server could generate errors during view execution with
110
- `200 OK` and partial results. By default the library raises exception as
111
- soon as errors detected in the result stream, but you can define the
112
- callback `on_error` to intercept these errors and do something more
113
- useful.
129
+ * `flags`
114
130
 
115
- view = blog.recent_posts
116
- logger = Logger.new(STDOUT)
131
+ * `cas`. The CAS version tag.
132
+
133
+ * `node`. Node address. It is used in flush and stats commands.
134
+
135
+ * `operation`. The symbol, representing an operation.
117
136
 
118
- view.on_error do |from, reason|
119
- logger.warn("#{view.inspect} received the error '#{reason}' from #{from}")
120
- end
121
137
 
122
- posts = view.each do |doc|
123
- # do something
124
- # with doc object
138
+ To handle global errors in async mode `#on_error` callback should be
139
+ used. It can be set in following fashions:
140
+
141
+ c.on_error do |opcode, key, exc|
142
+ ...
125
143
  end
126
144
 
127
- Note that errors object in view results usually goes *after* the rows,
128
- so you will likely receive a number of view results successfully before
129
- the error is detected.
145
+ handler = lambda {|opcode, key, exc| ...}
146
+ c.on_error = handler
147
+
148
+ By default connection uses `:quiet` mode. This mean it won't raise
149
+ exceptions when the given key is not exists:
150
+
151
+ c.get("missing-key") #=> nil
152
+
153
+ It could be useful when you are trying to make you code a bit efficient
154
+ by avoiding exception handling. (See `#add` and `#replace` operations).
155
+ You can turn on these exception by passing `:quiet => false` when you
156
+ are instantiating the connection or change corresponding attribute:
157
+
158
+ c.quiet = false
159
+ c.get("missing-key") #=> raise Couchbase::Error::NotFound
160
+ c.get("missing-key", :quiet => true) #=> nil
161
+
162
+ The library supports three different formats for representing values:
163
+
164
+ * `:document` (default) format supports most of ruby types which could
165
+ be mapped to JSON data (hashes, arrays, string, numbers). A future
166
+ version will be able to run map/reduce queries on the values in the
167
+ document form (hashes)
168
+
169
+ * `:marshal` This format avoids any conversions to be applied to your
170
+ data, but your data should be passed as String. This is useful for
171
+ building custom algorithms or formats. For example to implement a set:
172
+ http://dustin.github.com/2011/02/17/memcached-set.html
173
+
174
+ * `:plain` Use this format if you'd like to transparently serialize your
175
+ ruby object with standard `Marshal.dump` and `Marshal.load` methods
176
+
177
+ The couchbase API is the superset of [Memcached binary protocol][4], so
178
+ you can use its operations.
179
+
180
+ ### Get
181
+
182
+ val = c.get("foo")
183
+ val, flags, cas = c.get("foo", :extended => true)
184
+
185
+ Get and touch
186
+
187
+ val = c.get("foo", :ttl => 10)
188
+
189
+ Get multiple values. In quiet mode will put `nil` values on missing
190
+ positions:
191
+
192
+ vals = c.get("foo", "bar", "baz")
193
+ c.get("foo"){|val, key| ... }
194
+ c.get("foo", :extended => true){|val, key, flags, cas| ... }
195
+
196
+ Get multiple values with extended information. The result will
197
+ represented by hash with tuples `[value, flags, cas]` as a value.
198
+
199
+ vals = c.get("foo", "bar", "baz", :extended => true)
200
+ vals.inspect #=> {"baz"=>["3", 0, 4784582192793125888],
201
+ "foo"=>["1", 0, 8835713818674332672],
202
+ "bar"=>["2", 0, 10805929834096100352]}
203
+
204
+ Hash-like syntax
205
+
206
+ c["foo"]
207
+ c["foo", "bar", "baz"]
208
+ c["foo", {:extended => true}]
209
+ c["foo", :extended => true] # for ruby 1.9.x only
210
+
211
+ ### Touch
212
+
213
+ c.touch("foo") # use :default_ttl
214
+ c.touch("foo", 10)
215
+ c.touch("foo", :ttl => 10)
216
+ c.touch("foo" => 10, "bar" => 20)
217
+ c.touch("foo" => 10, "bar" => 20){|key, success| ... }
218
+
219
+ ### Set
220
+
221
+ c.set("foo", "bar")
222
+ c.set("foo", "bar", :flags => 0x1000, :ttl => 30, :format => :plain)
223
+ c["foo"] = "bar"
224
+ c["foo", {:flags => 0x1000, :format => :plain}] = "bar"
225
+ c["foo", :flags => 0x1000] = "bar" # for ruby 1.9.x only
226
+ c.set("foo", "bar", :cas => 8835713818674332672)
227
+ c.set("foo", "bar"){|cas, key, operation| ... }
228
+
229
+ ### Add
230
+
231
+ Add command will fail if the key already exists. It accepts the same
232
+ options as set command above.
233
+
234
+ c.add("foo", "bar")
235
+ c.add("foo", "bar", :flags => 0x1000, :ttl => 30, :format => :plain)
236
+
237
+ ### Replace
238
+
239
+ The replace command will fail if the key already exists. It accepts the same
240
+ options as set command above.
241
+
242
+ c.replace("foo", "bar")
243
+
244
+ ### Prepend/Append
245
+
246
+ These commands are meaningful when you are using the `:plain` value format,
247
+ because the concatenation is performed by server which has no idea how
248
+ to merge to JSON values or values in ruby Marshal format. You may receive
249
+ an `Couchbase::Error::ValueFormat` error.
250
+
251
+ c.set("foo", "world")
252
+ c.append("foo", "!")
253
+ c.prepend("foo", "Hello, ")
254
+ c.get("foo") #=> "Hello, world!"
255
+
256
+ ### Increment/Decrement
257
+
258
+ These commands increment the value assigned to the key. It will raise
259
+ Couchbase::Error::DeltaBadval if the delta or value is not a number.
260
+
261
+ c.set("foo", 1)
262
+ c.incr("foo") #=> 2
263
+ c.incr("foo", :delta => 2) #=> 4
264
+ c.incr("foo", 4) #=> 8
265
+ c.incr("foo", -1) #=> 7
266
+ c.incr("foo", -100) #=> 0
267
+ c.incr("foo"){|val, cas| ... }
268
+
269
+ c.set("foo", 10)
270
+ c.decr("foo", 1) #=> 9
271
+ c.decr("foo", 100) #=> 0
272
+ c.decr("foo"){|val, cas| ... }
273
+
274
+ c.incr("missing1", :initial => 10) #=> 10
275
+ c.incr("missing1", :initial => 10) #=> 11
276
+ c.incr("missing2", :create => true) #=> 0
277
+ c.incr("missing2", :create => true) #=> 1
278
+
279
+ Note that it isn't the same as increment/decrement in ruby, which is
280
+ performed on client side with following `set` operation:
281
+
282
+ c["foo"] = 10
283
+ c["foo"] -= 20 #=> -10
284
+
285
+ ### Delete
286
+
287
+ c.delete("foo")
288
+ c.delete("foo", :cas => 8835713818674332672)
289
+ c.delete("foo", 8835713818674332672)
290
+ c.delete{|key, success| ... }
291
+
292
+ ### Flush
293
+
294
+ Flush the items in the cluster.
295
+
296
+ c.flush
297
+ c.flush{|node, success| ... }
298
+
299
+ ### Stats
300
+
301
+ Return statistics from each node in the cluster
302
+
303
+ c.stats
304
+ c.stats{|node, key, value| ... }
305
+
306
+ The result is represented as a hash with the server node address as
307
+ the key and stats as key-value pairs.
308
+
309
+ {
310
+ "172.16.16.76:12008"=>
311
+ {
312
+ "threads"=>"4",
313
+ "connection_structures"=>"22",
314
+ "ep_max_txn_size"=>"10000",
315
+ ...
316
+ },
317
+ "172.16.16.76:12000"=>
318
+ {
319
+ "threads"=>"4",
320
+ "connection_structures"=>"447",
321
+ "ep_max_txn_size"=>"10000",
322
+ ...
323
+ },
324
+ ...
325
+ }
130
326
 
131
327
  [1]: https://github.com/brianmario/yajl-ruby/
132
- [2]: https://github.com/avsej/yaji/
133
- [3]: http://lloyd.github.com/yajl/
134
- [4]: https://rubygems.org/gems/curb/
135
- [5]: https://github.com/fauna/memcached/
328
+ [2]: http://lloyd.github.com/yajl/
329
+ [3]: http://www.couchbase.com/develop/c/current
330
+ [4]: http://code.google.com/p/memcached/wiki/BinaryProtocolRevamped