jmoses-couchbase 1.3.6

Sign up to get free protection for your applications and to get access to all the features.
Files changed (91) hide show
  1. data/.gitignore +15 -0
  2. data/.travis.yml +22 -0
  3. data/.yardopts +5 -0
  4. data/CONTRIBUTING.markdown +75 -0
  5. data/Gemfile +4 -0
  6. data/LICENSE +201 -0
  7. data/Makefile +3 -0
  8. data/README.markdown +665 -0
  9. data/RELEASE_NOTES.markdown +819 -0
  10. data/Rakefile +20 -0
  11. data/couchbase.gemspec +49 -0
  12. data/examples/chat-em/Gemfile +7 -0
  13. data/examples/chat-em/README.markdown +45 -0
  14. data/examples/chat-em/server.rb +82 -0
  15. data/examples/chat-goliath-grape/Gemfile +5 -0
  16. data/examples/chat-goliath-grape/README.markdown +50 -0
  17. data/examples/chat-goliath-grape/app.rb +67 -0
  18. data/examples/chat-goliath-grape/config/app.rb +20 -0
  19. data/examples/transcoders/Gemfile +3 -0
  20. data/examples/transcoders/README.markdown +59 -0
  21. data/examples/transcoders/cb-zcat +40 -0
  22. data/examples/transcoders/cb-zcp +45 -0
  23. data/examples/transcoders/gzip_transcoder.rb +49 -0
  24. data/examples/transcoders/options.rb +54 -0
  25. data/ext/couchbase_ext/.gitignore +4 -0
  26. data/ext/couchbase_ext/arguments.c +956 -0
  27. data/ext/couchbase_ext/arithmetic.c +316 -0
  28. data/ext/couchbase_ext/bucket.c +1373 -0
  29. data/ext/couchbase_ext/context.c +65 -0
  30. data/ext/couchbase_ext/couchbase_ext.c +1364 -0
  31. data/ext/couchbase_ext/couchbase_ext.h +644 -0
  32. data/ext/couchbase_ext/delete.c +163 -0
  33. data/ext/couchbase_ext/eventmachine_plugin.c +452 -0
  34. data/ext/couchbase_ext/extconf.rb +169 -0
  35. data/ext/couchbase_ext/get.c +316 -0
  36. data/ext/couchbase_ext/gethrtime.c +129 -0
  37. data/ext/couchbase_ext/http.c +432 -0
  38. data/ext/couchbase_ext/multithread_plugin.c +1090 -0
  39. data/ext/couchbase_ext/observe.c +171 -0
  40. data/ext/couchbase_ext/plugin_common.c +171 -0
  41. data/ext/couchbase_ext/result.c +129 -0
  42. data/ext/couchbase_ext/stats.c +163 -0
  43. data/ext/couchbase_ext/store.c +542 -0
  44. data/ext/couchbase_ext/timer.c +192 -0
  45. data/ext/couchbase_ext/touch.c +186 -0
  46. data/ext/couchbase_ext/unlock.c +176 -0
  47. data/ext/couchbase_ext/utils.c +551 -0
  48. data/ext/couchbase_ext/version.c +142 -0
  49. data/lib/action_dispatch/middleware/session/couchbase_store.rb +38 -0
  50. data/lib/active_support/cache/couchbase_store.rb +430 -0
  51. data/lib/couchbase.rb +155 -0
  52. data/lib/couchbase/bucket.rb +457 -0
  53. data/lib/couchbase/cluster.rb +119 -0
  54. data/lib/couchbase/connection_pool.rb +58 -0
  55. data/lib/couchbase/constants.rb +12 -0
  56. data/lib/couchbase/result.rb +26 -0
  57. data/lib/couchbase/transcoder.rb +120 -0
  58. data/lib/couchbase/utils.rb +62 -0
  59. data/lib/couchbase/version.rb +21 -0
  60. data/lib/couchbase/view.rb +506 -0
  61. data/lib/couchbase/view_row.rb +272 -0
  62. data/lib/ext/multi_json_fix.rb +56 -0
  63. data/lib/rack/session/couchbase.rb +108 -0
  64. data/tasks/benchmark.rake +6 -0
  65. data/tasks/compile.rake +160 -0
  66. data/tasks/test.rake +100 -0
  67. data/tasks/util.rake +21 -0
  68. data/test/profile/.gitignore +1 -0
  69. data/test/profile/Gemfile +6 -0
  70. data/test/profile/benchmark.rb +195 -0
  71. data/test/setup.rb +178 -0
  72. data/test/test_arithmetic.rb +185 -0
  73. data/test/test_async.rb +316 -0
  74. data/test/test_bucket.rb +276 -0
  75. data/test/test_cas.rb +235 -0
  76. data/test/test_couchbase.rb +77 -0
  77. data/test/test_couchbase_connection_pool.rb +77 -0
  78. data/test/test_couchbase_rails_cache_store.rb +361 -0
  79. data/test/test_delete.rb +120 -0
  80. data/test/test_errors.rb +82 -0
  81. data/test/test_eventmachine.rb +70 -0
  82. data/test/test_format.rb +164 -0
  83. data/test/test_get.rb +407 -0
  84. data/test/test_stats.rb +57 -0
  85. data/test/test_store.rb +216 -0
  86. data/test/test_timer.rb +42 -0
  87. data/test/test_touch.rb +97 -0
  88. data/test/test_unlock.rb +119 -0
  89. data/test/test_utils.rb +58 -0
  90. data/test/test_version.rb +52 -0
  91. metadata +353 -0
@@ -0,0 +1,142 @@
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
+ cb_version_callback(lcb_t handle, const void *cookie, lcb_error_t error, const lcb_server_version_resp_t *resp)
22
+ {
23
+ struct cb_context_st *ctx = (struct cb_context_st *)cookie;
24
+ struct cb_bucket_st *bucket = ctx->bucket;
25
+ VALUE node, val, 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, cb_id_iv_operation, cb_sym_version);
31
+ ctx->exception = exc;
32
+ }
33
+
34
+ if (node != Qnil) {
35
+ val = STR_NEW((const char*)resp->v.v0.vstring, resp->v.v0.nvstring);
36
+ if (bucket->async) { /* asynchronous */
37
+ if (ctx->proc != Qnil) {
38
+ res = rb_class_new_instance(0, NULL, cb_cResult);
39
+ rb_ivar_set(res, cb_id_iv_error, exc);
40
+ rb_ivar_set(res, cb_id_iv_operation, cb_sym_version);
41
+ rb_ivar_set(res, cb_id_iv_node, node);
42
+ rb_ivar_set(res, cb_id_iv_value, val);
43
+ cb_proc_call(bucket, ctx->proc, 1, res);
44
+ }
45
+ } else { /* synchronous */
46
+ if (NIL_P(exc)) {
47
+ rb_hash_aset(ctx->rv, node, val);
48
+ }
49
+ }
50
+ } else {
51
+ ctx->nqueries--;
52
+ ctx->proc = Qnil;
53
+ if (bucket->async) {
54
+ cb_context_free(ctx);
55
+ }
56
+ }
57
+
58
+ (void)handle;
59
+ }
60
+
61
+ /*
62
+ * Returns versions of the server for each node in the cluster
63
+ *
64
+ * @since 1.1.0
65
+ *
66
+ * @overload version
67
+ * @yieldparam [Result] ret the object with +error+, +node+, +operation+
68
+ * and +value+ attributes.
69
+ *
70
+ * @return [Hash] node-version pairs
71
+ *
72
+ * @raise [Couchbase::Error::Connect] if connection closed (see {Bucket#reconnect})
73
+ * @raise [ArgumentError] when passing the block in synchronous mode
74
+ *
75
+ * @example Synchronous version request
76
+ * c.version #=> will render version
77
+ *
78
+ * @example Asynchronous version request
79
+ * c.run do
80
+ * c.version do |ret|
81
+ * ret.operation #=> :version
82
+ * ret.success? #=> true
83
+ * ret.node #=> "localhost:11211"
84
+ * ret.value #=> will render version
85
+ * end
86
+ * end
87
+ */
88
+ VALUE
89
+ cb_bucket_version(int argc, VALUE *argv, VALUE self)
90
+ {
91
+ struct cb_bucket_st *bucket = DATA_PTR(self);
92
+ struct cb_context_st *ctx;
93
+ VALUE rv, exc, proc;
94
+ lcb_error_t err;
95
+ struct cb_params_st params;
96
+
97
+ if (!cb_bucket_connected_bang(bucket, cb_sym_version)) {
98
+ return Qnil;
99
+ }
100
+
101
+ memset(&params, 0, sizeof(struct cb_params_st));
102
+ rb_scan_args(argc, argv, "0*&", &params.args, &proc);
103
+ if (!bucket->async && proc != Qnil) {
104
+ rb_raise(rb_eArgError, "synchronous mode doesn't support callbacks");
105
+ }
106
+ params.type = cb_cmd_version;
107
+ params.bucket = bucket;
108
+ cb_params_build(&params);
109
+ ctx = cb_context_alloc_common(bucket, proc, params.cmd.version.num);
110
+ err = lcb_server_versions(bucket->handle, (const void *)ctx,
111
+ params.cmd.version.num, params.cmd.version.ptr);
112
+ exc = cb_check_error(err, "failed to schedule version request", Qnil);
113
+ cb_params_destroy(&params);
114
+ if (exc != Qnil) {
115
+ cb_context_free(ctx);
116
+ rb_exc_raise(exc);
117
+ }
118
+ bucket->nbytes += params.npayload;
119
+ if (bucket->async) {
120
+ cb_maybe_do_loop(bucket);
121
+ return Qnil;
122
+ } else {
123
+ if (ctx->nqueries > 0) {
124
+ /* we have some operations pending */
125
+ lcb_wait(bucket->handle);
126
+ }
127
+ exc = ctx->exception;
128
+ rv = ctx->rv;
129
+ cb_context_free(ctx);
130
+ if (exc != Qnil) {
131
+ rb_exc_raise(exc);
132
+ }
133
+ exc = bucket->exception;
134
+ if (exc != Qnil) {
135
+ bucket->exception = Qnil;
136
+ rb_exc_raise(exc);
137
+ }
138
+ return rv;
139
+ }
140
+ }
141
+
142
+
@@ -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_store :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_store :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,430 @@
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
+ require 'monitor'
23
+
24
+ module ActiveSupport
25
+ module Cache
26
+ # This class implements Cache interface for Rails. To use it just
27
+ # put following line in your config/application.rb file:
28
+ #
29
+ # config.cache_store = :couchbase_store
30
+ #
31
+ # You can also pass additional connection options there
32
+ #
33
+ # cache_options = {
34
+ # :bucket => 'protected',
35
+ # :username => 'protected',
36
+ # :password => 'secret',
37
+ # :expires_in => 30.seconds
38
+ # }
39
+ # config.cache_store = :couchbase_store, cache_options
40
+ class CouchbaseStore < Store
41
+
42
+ # Creates a new CouchbaseStore object, with the given options. For
43
+ # more info see {{Couchbase::Bucket#initialize}}
44
+ #
45
+ # ActiveSupport::Cache::CouchbaseStore.new(:bucket => "cache")
46
+ #
47
+ # If no options are specified, then CouchbaseStore will connect to
48
+ # localhost port 8091 (default Couchbase Server port) and will use
49
+ # bucket named "default" which is always open for unauthorized access
50
+ # (if exists).
51
+ def initialize(*args)
52
+ args = [*(args.flatten)]
53
+ options = args.extract_options! || {}
54
+ @raise_errors = !options[:quiet] = !options.delete(:raise_errors)
55
+ options[:default_ttl] ||= options.delete(:expires_in)
56
+ options[:default_format] ||= :marshal
57
+ options[:key_prefix] ||= options.delete(:namespace)
58
+ @key_prefix = options[:key_prefix]
59
+ options[:connection_pool] ||= options.delete(:connection_pool)
60
+ args.push(options)
61
+
62
+ if options[:connection_pool]
63
+ if RUBY_VERSION.to_f < 1.9
64
+ warn "connection_pool gem doesn't support ruby < 1.9"
65
+ else
66
+ @data = ::Couchbase::ConnectionPool.new(options[:connection_pool], *args)
67
+ end
68
+ end
69
+ unless @data
70
+ @data = ::Couchbase::Bucket.new(*args)
71
+ @data.extend(Threadsafe)
72
+ end
73
+ end
74
+
75
+ # Fetches data from the cache, using the given key.
76
+ #
77
+ # @since 1.2.0.dp5
78
+ #
79
+ # If there is data in the cache with the given key, then that data is
80
+ # returned. If there is no such data in the cache (a cache miss),
81
+ # then nil will be returned. However, if a block has been passed, that
82
+ # block will be run in the event of a cache miss. The return value of
83
+ # the block will be written to the cache under the given cache key,
84
+ # and that return value will be returned.
85
+ #
86
+ # @param [String] name name for the key
87
+ # @param [Hash] options
88
+ # @option options [true, false] :force if this option is +true+ it
89
+ # will force cache miss.
90
+ # @option options [Fixnum] :expires_in the expiration time on the
91
+ # cache in seconds. Values larger than 30*24*60*60 seconds (30 days)
92
+ # are interpreted as absolute times (from the epoch).
93
+ # @option options [true, false] :unless_exists if this option is +true+
94
+ # it will write value only if the key doesn't exist in the database
95
+ # (it accepts +:unless_exist+ too).
96
+ #
97
+ # @return [Object]
98
+ def fetch(name, options = nil)
99
+ options ||= {}
100
+ name = expanded_key(name)
101
+
102
+ if block_given?
103
+ unless options[:force]
104
+ entry = instrument(:read, name, options) do |payload|
105
+ payload[:super_operation] = :fetch if payload
106
+ read_entry(name, options)
107
+ end
108
+ end
109
+
110
+ if !entry.nil?
111
+ instrument(:fetch_hit, name, options) { |payload| }
112
+ entry
113
+ else
114
+ result = instrument(:generate, name, options) do |payload|
115
+ yield
116
+ end
117
+ write(name, result, options)
118
+ result
119
+ end
120
+ else
121
+ read(name, options)
122
+ end
123
+ end
124
+
125
+ # Writes the value to the cache, with the key
126
+ #
127
+ # @since 1.2.0.dp5
128
+ #
129
+ # @param [String] name name for the key
130
+ # @param [Object] value value of the key
131
+ # @param [Hash] options
132
+ # @option options [Fixnum] :expires_in the expiration time on the
133
+ # cache in seconds. Values larger than 30*24*60*60 seconds (30 days)
134
+ # are interpreted as absolute times (from the epoch).
135
+ #
136
+ # @return [Fixnum, false] false in case of failure and CAS value
137
+ # otherwise (it could be used as true value)
138
+ def write(name, value, options = nil)
139
+ options ||= {}
140
+ name = expanded_key name
141
+ if options.delete(:raw)
142
+ options[:format] = :plain
143
+ value = value.to_s
144
+ value.force_encoding(Encoding::BINARY) if defined?(Encoding)
145
+ end
146
+
147
+ instrument(:write, name, options) do |payload|
148
+ write_entry(name, value, options)
149
+ end
150
+ end
151
+
152
+ # Fetches data from the cache, using the given key.
153
+ #
154
+ # @since 1.2.0.dp5
155
+ #
156
+ # If there is data in the cache with the given key, then that data is
157
+ # returned. Otherwise, nil is returned.
158
+ #
159
+ # @param [String] name name for the key
160
+ # @param [Hash] options
161
+ # @option options [Fixnum] :expires_in the expiration time on the
162
+ # cache in seconds. Values larger than 30*24*60*60 seconds (30 days)
163
+ # are interpreted as absolute times (from the epoch).
164
+ # @option options [true, false] :raw do not marshal the value if this
165
+ # option is +true+
166
+ #
167
+ # @return [Object]
168
+ def read(name, options = nil)
169
+ options ||= {}
170
+ name = expanded_key name
171
+ if options.delete(:raw)
172
+ options[:format] = :plain
173
+ end
174
+
175
+ instrument(:read, name, options) do |payload|
176
+ entry = read_entry(name, options)
177
+ payload[:hit] = !!entry if payload
178
+ entry
179
+ end
180
+ end
181
+
182
+ # Read multiple values at once from the cache.
183
+ #
184
+ # @since 1.2.0.dp5
185
+ #
186
+ # Options can be passed in the last argument.
187
+ #
188
+ # Returns a hash mapping the names provided to the values found.
189
+ #
190
+ # @return [Hash] key-value pairs
191
+ def read_multi(*names)
192
+ options = names.extract_options!
193
+ names = names.flatten.map{|name| expanded_key(name)}
194
+ options[:assemble_hash] = true
195
+ if options.delete(:raw)
196
+ options[:format] = :plain
197
+ end
198
+ instrument(:read_multi, names, options) do
199
+ @data.get(names, options)
200
+ end
201
+ rescue ::Couchbase::Error::Base => e
202
+ logger.error("#{e.class}: #{e.message}") if logger
203
+ raise if @raise_errors
204
+ false
205
+ end
206
+
207
+ # Return true if the cache contains an entry for the given key.
208
+ #
209
+ # @since 1.2.0.dp5
210
+ #
211
+ # @return [true, false]
212
+ def exists?(name, options = nil)
213
+ options ||= {}
214
+ name = expanded_key name
215
+
216
+ instrument(:exists?, name) do
217
+ !read_entry(name, options).nil?
218
+ end
219
+ end
220
+ alias :exist? :exists?
221
+
222
+ # Deletes an entry in the cache.
223
+ #
224
+ # @since 1.2.0.dp5
225
+ #
226
+ # @return [true, false] true if an entry is deleted
227
+ def delete(name, options = nil)
228
+ options ||= {}
229
+ name = expanded_key name
230
+
231
+ instrument(:delete, name) do
232
+ delete_entry(name, options)
233
+ end
234
+ end
235
+
236
+ # Increment an integer value in the cache.
237
+ #
238
+ # @since 1.2.0.dp5
239
+ #
240
+ # @param [String] name name for the key
241
+ # @param [Fixnum] amount (1) the delta value
242
+ # @param [Hash] options
243
+ # @option options [Fixnum] :expires_in the expiration time on the
244
+ # cache in seconds. Values larger than 30*24*60*60 seconds (30 days)
245
+ # are interpreted as absolute times (from the epoch).
246
+ # @option options [Fixnum] :initial (1) this option allows to initialize
247
+ # the value if the key is missing in the cache
248
+ #
249
+ # @return [Fixnum] new value
250
+ def increment(name, amount = 1, options = nil)
251
+ options ||= {}
252
+ name = expanded_key name
253
+
254
+ if ttl = options.delete(:expires_in)
255
+ options[:ttl] ||= ttl
256
+ end
257
+ options[:create] = true
258
+ instrument(:increment, name, options) do |payload|
259
+ payload[:amount] = amount if payload
260
+ @data.incr(name, amount, options)
261
+ end
262
+ rescue ::Couchbase::Error::Base => e
263
+ logger.error("#{e.class}: #{e.message}") if logger
264
+ raise if @raise_errors
265
+ false
266
+ end
267
+
268
+ # Decrement an integer value in the cache.
269
+ #
270
+ # @since 1.2.0.dp5
271
+ #
272
+ # @param [String] name name for the key
273
+ # @param [Fixnum] amount (1) the delta value
274
+ # @param [Hash] options
275
+ # @option options [Fixnum] :expires_in the expiration time on the
276
+ # cache in seconds. Values larger than 30*24*60*60 seconds (30 days)
277
+ # are interpreted as absolute times (from the epoch).
278
+ # @option options [Fixnum] :initial this option allows to initialize
279
+ # the value if the key is missing in the cache
280
+ #
281
+ # @return [Fixnum] new value
282
+ def decrement(name, amount = 1, options = nil)
283
+ options ||= {}
284
+ name = expanded_key name
285
+
286
+ if ttl = options.delete(:expires_in)
287
+ options[:ttl] ||= ttl
288
+ end
289
+ options[:create] = true
290
+ instrument(:decrement, name, options) do |payload|
291
+ payload[:amount] = amount if payload
292
+ @data.decr(name, amount, options)
293
+ end
294
+ rescue ::Couchbase::Error::Base => e
295
+ logger.error("#{e.class}: #{e.message}") if logger
296
+ raise if @raise_errors
297
+ false
298
+ end
299
+
300
+ # Get the statistics from the memcached servers.
301
+ #
302
+ # @since 1.2.0.dp5
303
+ #
304
+ # @return [Hash]
305
+ def stats(*arg)
306
+ @data.stats(*arg)
307
+ end
308
+
309
+ protected
310
+
311
+ # Read an entry from the cache.
312
+ def read_entry(key, options) # :nodoc:
313
+ @data.get(key, options)
314
+ rescue ::Couchbase::Error::Base => e
315
+ logger.error("#{e.class}: #{e.message}") if logger
316
+ raise if @raise_errors
317
+ nil
318
+ end
319
+
320
+ # Write an entry to the cache.
321
+ def write_entry(key, value, options) # :nodoc:
322
+ method = if options[:unless_exists] || options[:unless_exist]
323
+ :add
324
+ else
325
+ :set
326
+ end
327
+ if ttl = options.delete(:expires_in)
328
+ options[:ttl] ||= ttl
329
+ end
330
+ @data.send(method, key, value, options)
331
+ rescue ::Couchbase::Error::Base => e
332
+ logger.error("#{e.class}: #{e.message}") if logger
333
+ raise if @raise_errors
334
+ false
335
+ end
336
+
337
+ # Delete an entry from the cache.
338
+ def delete_entry(key, options) # :nodoc:
339
+ @data.delete(key, options)
340
+ rescue ::Couchbase::Error::Base => e
341
+ logger.error("#{e.class}: #{e.message}") if logger
342
+ raise if @raise_errors
343
+ false
344
+ end
345
+
346
+ private
347
+
348
+ # Expand key to be a consistent string value. Invoke +cache_key+ if
349
+ # object responds to +cache_key+. Otherwise, to_param method will be
350
+ # called. If the key is a Hash, then keys will be sorted alphabetically.
351
+ def expanded_key(key) # :nodoc:
352
+ return validate_key(key.cache_key.to_s) if key.respond_to?(:cache_key)
353
+
354
+ case key
355
+ when Array
356
+ if key.size > 1
357
+ key = key.collect{|element| expanded_key(element)}
358
+ else
359
+ key = key.first
360
+ end
361
+ when Hash
362
+ key = key.sort_by { |k,_| k.to_s }.collect{|k,v| "#{k}=#{v}"}
363
+ end
364
+
365
+ validate_key(key.respond_to?(:to_param) ? key.to_param : key)
366
+ end
367
+
368
+ def validate_key(key)
369
+ if key_with_prefix(key).length > 250
370
+ key = "#{key[0, max_length_before_prefix]}:md5:#{Digest::MD5.hexdigest(key)}"
371
+ end
372
+ return key
373
+ end
374
+
375
+ def key_with_prefix(key)
376
+ (ns = @key_prefix) ? "#{ns}#{key}" : key
377
+ end
378
+
379
+ def max_length_before_prefix
380
+ @max_length_before_prefix ||= 212 - (@key_prefix || '').size
381
+ end
382
+
383
+ module Threadsafe
384
+ def self.extended(obj)
385
+ obj.init_threadsafe
386
+ end
387
+
388
+ def get(*)
389
+ @lock.synchronize do
390
+ super
391
+ end
392
+ end
393
+
394
+ def send(*)
395
+ @lock.synchronize do
396
+ super
397
+ end
398
+ end
399
+
400
+ def delete(*)
401
+ @lock.synchronize do
402
+ super
403
+ end
404
+ end
405
+
406
+ def incr(*)
407
+ @lock.synchronize do
408
+ super
409
+ end
410
+ end
411
+
412
+ def decr(*)
413
+ @lock.synchronize do
414
+ super
415
+ end
416
+ end
417
+
418
+ def stats(*)
419
+ @lock.synchronize do
420
+ super
421
+ end
422
+ end
423
+
424
+ def init_threadsafe
425
+ @lock = Monitor.new
426
+ end
427
+ end
428
+ end
429
+ end
430
+ end