couchbase 1.1.5-x86-mingw32 → 1.2.0.beta-x86-mingw32

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 (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 +102 -12
@@ -0,0 +1,147 @@
1
+ /* vim: ft=c et ts=8 sts=4 sw=4 cino=
2
+ *
3
+ * Copyright 2011, 2012 Couchbase, Inc.
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+
18
+ #include "couchbase_ext.h"
19
+
20
+ void
21
+ version_callback(lcb_t handle, const void *cookie, lcb_error_t error, const lcb_server_version_resp_t *resp)
22
+ {
23
+ struct context_st *ctx = (struct context_st *)cookie;
24
+ struct bucket_st *bucket = ctx->bucket;
25
+ VALUE node, val, *rv = ctx->rv, exc, res;
26
+
27
+ node = resp->v.v0.server_endpoint ? STR_NEW_CSTR(resp->v.v0.server_endpoint) : Qnil;
28
+ exc = cb_check_error(error, "failed to get version", node);
29
+ if (exc != Qnil) {
30
+ rb_ivar_set(exc, id_iv_operation, sym_version);
31
+ if (NIL_P(ctx->exception)) {
32
+ ctx->exception = cb_gc_protect(bucket, exc);
33
+ }
34
+ }
35
+
36
+ if (node != Qnil) {
37
+ val = STR_NEW((const char*)resp->v.v0.vstring, resp->v.v0.nvstring);
38
+ if (bucket->async) { /* asynchronous */
39
+ if (ctx->proc != Qnil) {
40
+ res = rb_class_new_instance(0, NULL, cResult);
41
+ rb_ivar_set(res, id_iv_error, exc);
42
+ rb_ivar_set(res, id_iv_operation, sym_version);
43
+ rb_ivar_set(res, id_iv_node, node);
44
+ rb_ivar_set(res, id_iv_value, val);
45
+ cb_proc_call(ctx->proc, 1, res);
46
+ }
47
+ } else { /* synchronous */
48
+ if (NIL_P(exc)) {
49
+ rb_hash_aset(*rv, node, val);
50
+ }
51
+ }
52
+ } else {
53
+ ctx->nqueries--;
54
+ cb_gc_unprotect(bucket, ctx->proc);
55
+ }
56
+
57
+ (void)handle;
58
+ }
59
+
60
+ /*
61
+ * Returns versions of the server for each node in the cluster
62
+ *
63
+ * @since 1.1.0
64
+ *
65
+ * @overload version
66
+ * @yieldparam [Result] ret the object with +error+, +node+, +operation+
67
+ * and +value+ attributes.
68
+ *
69
+ * @return [Hash] node-version pairs
70
+ *
71
+ * @raise [Couchbase::Error::Connect] if connection closed (see {Bucket#reconnect})
72
+ * @raise [ArgumentError] when passing the block in synchronous mode
73
+ *
74
+ * @example Synchronous version request
75
+ * c.version #=> will render version
76
+ *
77
+ * @example Asynchronous version request
78
+ * c.run do
79
+ * c.version do |ret|
80
+ * ret.operation #=> :version
81
+ * ret.success? #=> true
82
+ * ret.node #=> "localhost:11211"
83
+ * ret.value #=> will render version
84
+ * end
85
+ * end
86
+ */
87
+ VALUE
88
+ cb_bucket_version(int argc, VALUE *argv, VALUE self)
89
+ {
90
+ struct bucket_st *bucket = DATA_PTR(self);
91
+ struct context_st *ctx;
92
+ VALUE rv, exc, args, proc;
93
+ lcb_error_t err;
94
+ struct params_st params;
95
+
96
+ if (bucket->handle == NULL) {
97
+ rb_raise(eConnectError, "closed connection");
98
+ }
99
+ rb_scan_args(argc, argv, "0*&", &args, &proc);
100
+ if (!bucket->async && proc != Qnil) {
101
+ rb_raise(rb_eArgError, "synchronous mode doesn't support callbacks");
102
+ }
103
+ memset(&params, 0, sizeof(struct params_st));
104
+ params.type = cmd_version;
105
+ params.bucket = bucket;
106
+ cb_params_build(&params, RARRAY_LEN(args), args);
107
+ ctx = xcalloc(1, sizeof(struct context_st));
108
+ if (ctx == NULL) {
109
+ rb_raise(eClientNoMemoryError, "failed to allocate memory for context");
110
+ }
111
+ rv = rb_hash_new();
112
+ ctx->rv = &rv;
113
+ ctx->bucket = bucket;
114
+ ctx->exception = Qnil;
115
+ ctx->proc = cb_gc_protect(bucket, proc);
116
+ ctx->nqueries = params.cmd.version.num;
117
+ err = lcb_server_versions(bucket->handle, (const void *)ctx,
118
+ params.cmd.version.num, params.cmd.version.ptr);
119
+ exc = cb_check_error(err, "failed to schedule version request", Qnil);
120
+ cb_params_destroy(&params);
121
+ if (exc != Qnil) {
122
+ xfree(ctx);
123
+ rb_exc_raise(exc);
124
+ }
125
+ bucket->nbytes += params.npayload;
126
+ if (bucket->async) {
127
+ maybe_do_loop(bucket);
128
+ return Qnil;
129
+ } else {
130
+ if (ctx->nqueries > 0) {
131
+ /* we have some operations pending */
132
+ lcb_wait(bucket->handle);
133
+ }
134
+ exc = ctx->exception;
135
+ xfree(ctx);
136
+ if (exc != Qnil) {
137
+ cb_gc_unprotect(bucket, exc);
138
+ rb_exc_raise(exc);
139
+ }
140
+ if (bucket->exception != Qnil) {
141
+ rb_exc_raise(bucket->exception);
142
+ }
143
+ return rv;
144
+ }
145
+ }
146
+
147
+
@@ -0,0 +1,38 @@
1
+ require 'active_support/cache'
2
+ require 'action_dispatch/middleware/session/abstract_store'
3
+ require 'rack/session/couchbase'
4
+ require 'couchbase'
5
+
6
+ module ActionDispatch
7
+ module Session
8
+
9
+ # This is Couchbase-powered session store for Rails applications
10
+ #
11
+ # To use it just update your `config/initializers/session_store.rb` file
12
+ #
13
+ # require 'action_dispatch/middleware/session/couchbase_store'
14
+ # AppName::Application.config.session_store :couchbase_store
15
+ #
16
+ # Or remove this file and add following line to your `config/application.rb`:
17
+ #
18
+ # require 'action_dispatch/middleware/session/couchbase_store'
19
+ # config.session_storage = :couchbase_store
20
+ #
21
+ # You can also pass additional options:
22
+ #
23
+ # require 'action_dispatch/middleware/session/couchbase_store'
24
+ # session_options = {
25
+ # :expire_after => 5.minutes,
26
+ # :couchbase => {:bucket => "sessions", :default_format => :marshal}
27
+ # }
28
+ # config.session_storage = :couchbase_store, session_options
29
+ #
30
+ # By default sessions will be serialized to JSON, to allow analyse them
31
+ # using Map/Reduce.
32
+ #
33
+ class CouchbaseStore < Rack::Session::Couchbase
34
+ include Compatibility
35
+ include StaleSessionCheck
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,356 @@
1
+ # Author:: Couchbase <info@couchbase.com>
2
+ # Copyright:: 2011, 2012 Couchbase, Inc.
3
+ # License:: Apache License, Version 2.0
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
+ require 'couchbase'
19
+ require 'securerandom'
20
+ require 'active_support/core_ext/array/extract_options'
21
+ require 'active_support/cache'
22
+
23
+ module ActiveSupport
24
+ module Cache
25
+ # This class implements Cache interface for Rails. To use it just
26
+ # put following line in your config/application.rb file:
27
+ #
28
+ # config.cache_store = :couchbase_store
29
+ #
30
+ # You can also pass additional connection options there
31
+ #
32
+ # cache_options = {
33
+ # :bucket => 'protected',
34
+ # :username => 'protected',
35
+ # :password => 'secret',
36
+ # :expires_in => 30.seconds
37
+ # }
38
+ # config.cache_store = :couchbase_store, cache_options
39
+ class CouchbaseStore < Store
40
+
41
+ # Creates a new CouchbaseStore object, with the given options. For
42
+ # more info see {{Couchbase::Bucket#initialize}}
43
+ #
44
+ # ActiveSupport::Cache::CouchbaseStore.new(:bucket => "cache")
45
+ #
46
+ # If no options are specified, then CouchbaseStore will connect to
47
+ # localhost port 8091 (default Couchbase Server port) and will use
48
+ # bucket named "default" which is always open for unauthorized access
49
+ # (if exists).
50
+ def initialize(*args)
51
+ args = [*(args.flatten)]
52
+ options = args.extract_options! || {}
53
+ @raise_errors = !options[:quiet] = !options.delete(:raise_errors)
54
+ options[:default_ttl] ||= options.delete(:expires_in)
55
+ options[:default_format] ||= :marshal
56
+ options[:key_prefix] ||= options.delete(:namespace)
57
+ args.push(options)
58
+ @data = ::Couchbase::Bucket.new(*args)
59
+ end
60
+
61
+ # Fetches data from the cache, using the given key.
62
+ #
63
+ # @since 1.2.0.dp5
64
+ #
65
+ # If there is data in the cache with the given key, then that data is
66
+ # returned. If there is no such data in the cache (a cache miss),
67
+ # then nil will be returned. However, if a block has been passed, that
68
+ # block will be run in the event of a cache miss. The return value of
69
+ # the block will be written to the cache under the given cache key,
70
+ # and that return value will be returned.
71
+ #
72
+ # @param [String] name name for the key
73
+ # @param [Hash] options
74
+ # @option options [true, false] :force if this option is +true+ it
75
+ # will force cache miss.
76
+ # @option options [Fixnum] :expires_in the expiration time on the
77
+ # cache in seconds. Values larger than 30*24*60*60 seconds (30 days)
78
+ # are interpreted as absolute times (from the epoch).
79
+ # @option options [true, false] :unless_exists if this option is +true+
80
+ # it will write value only if the key doesn't exist in the database
81
+ # (it accepts +:unless_exist+ too).
82
+ #
83
+ # @return [Object]
84
+ def fetch(name, options = nil)
85
+ options ||= {}
86
+ name = expanded_key(name)
87
+
88
+ if block_given?
89
+ unless options[:force]
90
+ entry = instrument(:read, name, options) do |payload|
91
+ payload[:super_operation] = :fetch if payload
92
+ read_entry(name, options)
93
+ end
94
+ end
95
+
96
+ if !entry.nil?
97
+ instrument(:fetch_hit, name, options) { |payload| }
98
+ entry
99
+ else
100
+ result = instrument(:generate, name, options) do |payload|
101
+ yield
102
+ end
103
+ write(name, result, options)
104
+ result
105
+ end
106
+ else
107
+ read(name, options)
108
+ end
109
+ end
110
+
111
+ # Writes the value to the cache, with the key
112
+ #
113
+ # @since 1.2.0.dp5
114
+ #
115
+ # @param [String] name name for the key
116
+ # @param [Object] value value of the key
117
+ # @param [Hash] options
118
+ # @option options [Fixnum] :expires_in the expiration time on the
119
+ # cache in seconds. Values larger than 30*24*60*60 seconds (30 days)
120
+ # are interpreted as absolute times (from the epoch).
121
+ #
122
+ # @return [Fixnum, false] false in case of failure and CAS value
123
+ # otherwise (it could be used as true value)
124
+ def write(name, value, options = nil)
125
+ options ||= {}
126
+ name = expanded_key name
127
+ if options.delete(:raw)
128
+ options[:format] = :plain
129
+ value = value.to_s
130
+ value.force_encoding(Encoding::BINARY) if defined?(Encoding)
131
+ end
132
+
133
+ instrument(:write, name, options) do |payload|
134
+ write_entry(name, value, options)
135
+ end
136
+ end
137
+
138
+ # Fetches data from the cache, using the given key.
139
+ #
140
+ # @since 1.2.0.dp5
141
+ #
142
+ # If there is data in the cache with the given key, then that data is
143
+ # returned. Otherwise, nil is returned.
144
+ #
145
+ # @param [String] name name for the key
146
+ # @param [Hash] options
147
+ # @option options [Fixnum] :expires_in the expiration time on the
148
+ # cache in seconds. Values larger than 30*24*60*60 seconds (30 days)
149
+ # are interpreted as absolute times (from the epoch).
150
+ # @option options [true, false] :raw do not marshal the value if this
151
+ # option is +true+
152
+ #
153
+ # @return [Object]
154
+ def read(name, options = nil)
155
+ options ||= {}
156
+ name = expanded_key name
157
+ if options.delete(:raw)
158
+ options[:format] = :plain
159
+ end
160
+
161
+ instrument(:read, name, options) do |payload|
162
+ entry = read_entry(name, options)
163
+ payload[:hit] = !!entry if payload
164
+ entry
165
+ end
166
+ end
167
+
168
+ # Read multiple values at once from the cache.
169
+ #
170
+ # @since 1.2.0.dp5
171
+ #
172
+ # Options can be passed in the last argument.
173
+ #
174
+ # Returns a hash mapping the names provided to the values found.
175
+ #
176
+ # @return [Hash] key-value pairs
177
+ def read_multi(*names)
178
+ options = names.extract_options!
179
+ names = names.flatten.map{|name| expanded_key(name)}
180
+ options[:assemble_hash] = true
181
+ if options.delete(:raw)
182
+ options[:format] = :plain
183
+ end
184
+ instrument(:read_multi, names, options) do
185
+ @data.get(names, options)
186
+ end
187
+ rescue Couchbase::Error::Base => e
188
+ logger.error("#{e.class}: #{e.message}") if logger
189
+ raise if @raise_errors
190
+ false
191
+ end
192
+
193
+ # Return true if the cache contains an entry for the given key.
194
+ #
195
+ # @since 1.2.0.dp5
196
+ #
197
+ # @return [true, false]
198
+ def exists?(name, options = nil)
199
+ options ||= {}
200
+ name = expanded_key name
201
+
202
+ instrument(:exists?, name) do
203
+ !read_entry(name, options).nil?
204
+ end
205
+ end
206
+ alias :exist? :exists?
207
+
208
+ # Deletes an entry in the cache.
209
+ #
210
+ # @since 1.2.0.dp5
211
+ #
212
+ # @return [true, false] true if an entry is deleted
213
+ def delete(name, options = nil)
214
+ options ||= {}
215
+ name = expanded_key name
216
+
217
+ instrument(:delete, name) do
218
+ delete_entry(name, options)
219
+ end
220
+ end
221
+
222
+ # Increment an integer value in the cache.
223
+ #
224
+ # @since 1.2.0.dp5
225
+ #
226
+ # @param [String] name name for the key
227
+ # @param [Fixnum] amount (1) the delta value
228
+ # @param [Hash] options
229
+ # @option options [Fixnum] :expires_in the expiration time on the
230
+ # cache in seconds. Values larger than 30*24*60*60 seconds (30 days)
231
+ # are interpreted as absolute times (from the epoch).
232
+ # @option options [Fixnum] :initial (1) this option allows to initialize
233
+ # the value if the key is missing in the cache
234
+ #
235
+ # @return [Fixnum] new value
236
+ def increment(name, amount = 1, options = nil)
237
+ options ||= {}
238
+ name = expanded_key name
239
+
240
+ if ttl = options.delete(:expires_in)
241
+ options[:ttl] ||= ttl
242
+ end
243
+ options[:create] = true
244
+ instrument(:increment, name, options) do |payload|
245
+ payload[:amount] = amount if payload
246
+ @data.incr(name, amount, options)
247
+ end
248
+ rescue Couchbase::Error::Base => e
249
+ logger.error("#{e.class}: #{e.message}") if logger
250
+ raise if @raise_errors
251
+ false
252
+ end
253
+
254
+ # Decrement an integer value in the cache.
255
+ #
256
+ # @since 1.2.0.dp5
257
+ #
258
+ # @param [String] name name for the key
259
+ # @param [Fixnum] amount (1) the delta value
260
+ # @param [Hash] options
261
+ # @option options [Fixnum] :expires_in the expiration time on the
262
+ # cache in seconds. Values larger than 30*24*60*60 seconds (30 days)
263
+ # are interpreted as absolute times (from the epoch).
264
+ # @option options [Fixnum] :initial this option allows to initialize
265
+ # the value if the key is missing in the cache
266
+ #
267
+ # @return [Fixnum] new value
268
+ def decrement(name, amount = 1, options = nil)
269
+ options ||= {}
270
+ name = expanded_key name
271
+
272
+ if ttl = options.delete(:expires_in)
273
+ options[:ttl] ||= ttl
274
+ end
275
+ options[:create] = true
276
+ instrument(:decrement, name, options) do |payload|
277
+ payload[:amount] = amount if payload
278
+ @data.decr(name, amount, options)
279
+ end
280
+ rescue Couchbase::Error::Base => e
281
+ logger.error("#{e.class}: #{e.message}") if logger
282
+ raise if @raise_errors
283
+ false
284
+ end
285
+
286
+ # Get the statistics from the memcached servers.
287
+ #
288
+ # @since 1.2.0.dp5
289
+ #
290
+ # @return [Hash]
291
+ def stats(*arg)
292
+ @data.stats(*arg)
293
+ end
294
+
295
+ protected
296
+
297
+ # Read an entry from the cache.
298
+ def read_entry(key, options) # :nodoc:
299
+ @data.get(key, options)
300
+ rescue Couchbase::Error::Base => e
301
+ logger.error("#{e.class}: #{e.message}") if logger
302
+ raise if @raise_errors
303
+ nil
304
+ end
305
+
306
+ # Write an entry to the cache.
307
+ def write_entry(key, value, options) # :nodoc:
308
+ method = if options[:unless_exists] || options[:unless_exist]
309
+ :add
310
+ else
311
+ :set
312
+ end
313
+ if ttl = options.delete(:expires_in)
314
+ options[:ttl] ||= ttl
315
+ end
316
+ @data.send(method, key, value, options)
317
+ rescue Couchbase::Error::Base => e
318
+ logger.error("#{e.class}: #{e.message}") if logger
319
+ raise if @raise_errors
320
+ false
321
+ end
322
+
323
+ # Delete an entry from the cache.
324
+ def delete_entry(key, options) # :nodoc:
325
+ @data.delete(key, options)
326
+ rescue Couchbase::Error::Base => e
327
+ logger.error("#{e.class}: #{e.message}") if logger
328
+ raise if @raise_errors
329
+ false
330
+ end
331
+
332
+ private
333
+
334
+ # Expand key to be a consistent string value. Invoke +cache_key+ if
335
+ # object responds to +cache_key+. Otherwise, to_param method will be
336
+ # called. If the key is a Hash, then keys will be sorted alphabetically.
337
+ def expanded_key(key) # :nodoc:
338
+ return key.cache_key.to_s if key.respond_to?(:cache_key)
339
+
340
+ case key
341
+ when Array
342
+ if key.size > 1
343
+ key = key.collect{|element| expanded_key(element)}
344
+ else
345
+ key = key.first
346
+ end
347
+ when Hash
348
+ key = key.sort_by { |k,_| k.to_s }.collect{|k,v| "#{k}=#{v}"}
349
+ end
350
+
351
+ key.respond_to?(:to_param) ? key.to_param : key
352
+ end
353
+
354
+ end
355
+ end
356
+ end