couchbase 1.2.0.z.beta-x86-mingw32 → 1.2.1-x86-mingw32

Sign up to get free protection for your applications and to get access to all the features.
Files changed (45) hide show
  1. data/.travis.yml +1 -1
  2. data/Makefile +3 -0
  3. data/README.markdown +15 -4
  4. data/RELEASE_NOTES.markdown +526 -0
  5. data/couchbase.gemspec +0 -1
  6. data/ext/couchbase_ext/arguments.c +161 -244
  7. data/ext/couchbase_ext/arithmetic.c +29 -37
  8. data/ext/couchbase_ext/bucket.c +252 -219
  9. data/ext/couchbase_ext/couchbase_ext.c +540 -417
  10. data/ext/couchbase_ext/couchbase_ext.h +218 -191
  11. data/ext/couchbase_ext/delete.c +30 -27
  12. data/ext/couchbase_ext/extconf.rb +15 -3
  13. data/ext/couchbase_ext/get.c +45 -37
  14. data/ext/couchbase_ext/http.c +95 -74
  15. data/ext/couchbase_ext/multithread_plugin.c +1238 -0
  16. data/ext/couchbase_ext/observe.c +42 -37
  17. data/ext/couchbase_ext/result.c +17 -20
  18. data/ext/couchbase_ext/stats.c +30 -28
  19. data/ext/couchbase_ext/store.c +47 -39
  20. data/ext/couchbase_ext/timer.c +11 -11
  21. data/ext/couchbase_ext/touch.c +30 -27
  22. data/ext/couchbase_ext/unlock.c +30 -27
  23. data/ext/couchbase_ext/utils.c +166 -89
  24. data/ext/couchbase_ext/version.c +29 -26
  25. data/lib/action_dispatch/middleware/session/couchbase_store.rb +2 -2
  26. data/lib/active_support/cache/couchbase_store.rb +6 -6
  27. data/lib/couchbase.rb +1 -0
  28. data/lib/couchbase/bucket.rb +6 -11
  29. data/lib/couchbase/cluster.rb +105 -0
  30. data/lib/couchbase/utils.rb +8 -5
  31. data/lib/couchbase/version.rb +1 -1
  32. data/lib/couchbase/view.rb +51 -5
  33. data/lib/couchbase/view_row.rb +1 -1
  34. data/lib/ext/multi_json_fix.rb +13 -9
  35. data/lib/rack/session/couchbase.rb +11 -7
  36. data/tasks/compile.rake +1 -1
  37. data/tasks/test.rake +40 -34
  38. data/tasks/util.rake +1 -1
  39. data/test/setup.rb +9 -2
  40. data/test/test_arithmetic.rb +37 -0
  41. data/test/test_async.rb +22 -18
  42. data/test/test_unlock.rb +0 -1
  43. data/test/test_utils.rb +32 -0
  44. metadata +13 -23
  45. data/HISTORY.markdown +0 -219
@@ -18,31 +18,29 @@
18
18
  #include "couchbase_ext.h"
19
19
 
20
20
  void
21
- version_callback(lcb_t handle, const void *cookie, lcb_error_t error, const lcb_server_version_resp_t *resp)
21
+ cb_version_callback(lcb_t handle, const void *cookie, lcb_error_t error, const lcb_server_version_resp_t *resp)
22
22
  {
23
- struct context_st *ctx = (struct context_st *)cookie;
24
- struct bucket_st *bucket = ctx->bucket;
23
+ struct cb_context_st *ctx = (struct cb_context_st *)cookie;
24
+ struct cb_bucket_st *bucket = ctx->bucket;
25
25
  VALUE node, val, *rv = ctx->rv, exc, res;
26
26
 
27
27
  node = resp->v.v0.server_endpoint ? STR_NEW_CSTR(resp->v.v0.server_endpoint) : Qnil;
28
28
  exc = cb_check_error(error, "failed to get version", node);
29
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
- }
30
+ rb_ivar_set(exc, cb_id_iv_operation, cb_sym_version);
31
+ ctx->exception = cb_gc_protect(bucket, exc);
34
32
  }
35
33
 
36
34
  if (node != Qnil) {
37
35
  val = STR_NEW((const char*)resp->v.v0.vstring, resp->v.v0.nvstring);
38
36
  if (bucket->async) { /* asynchronous */
39
37
  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);
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);
46
44
  }
47
45
  } else { /* synchronous */
48
46
  if (NIL_P(exc)) {
@@ -52,6 +50,9 @@ version_callback(lcb_t handle, const void *cookie, lcb_error_t error, const lcb_
52
50
  } else {
53
51
  ctx->nqueries--;
54
52
  cb_gc_unprotect(bucket, ctx->proc);
53
+ if (bucket->async) {
54
+ free(ctx);
55
+ }
55
56
  }
56
57
 
57
58
  (void)handle;
@@ -87,26 +88,26 @@ version_callback(lcb_t handle, const void *cookie, lcb_error_t error, const lcb_
87
88
  VALUE
88
89
  cb_bucket_version(int argc, VALUE *argv, VALUE self)
89
90
  {
90
- struct bucket_st *bucket = DATA_PTR(self);
91
- struct context_st *ctx;
91
+ struct cb_bucket_st *bucket = DATA_PTR(self);
92
+ struct cb_context_st *ctx;
92
93
  VALUE rv, exc, args, proc;
93
94
  lcb_error_t err;
94
- struct params_st params;
95
+ struct cb_params_st params;
95
96
 
96
97
  if (bucket->handle == NULL) {
97
- rb_raise(eConnectError, "closed connection");
98
+ rb_raise(cb_eConnectError, "closed connection");
98
99
  }
99
100
  rb_scan_args(argc, argv, "0*&", &args, &proc);
100
101
  if (!bucket->async && proc != Qnil) {
101
102
  rb_raise(rb_eArgError, "synchronous mode doesn't support callbacks");
102
103
  }
103
- memset(&params, 0, sizeof(struct params_st));
104
- params.type = cmd_version;
104
+ memset(&params, 0, sizeof(struct cb_params_st));
105
+ params.type = cb_cmd_version;
105
106
  params.bucket = bucket;
106
107
  cb_params_build(&params, RARRAY_LEN(args), args);
107
- ctx = xcalloc(1, sizeof(struct context_st));
108
+ ctx = calloc(1, sizeof(struct cb_context_st));
108
109
  if (ctx == NULL) {
109
- rb_raise(eClientNoMemoryError, "failed to allocate memory for context");
110
+ rb_raise(cb_eClientNoMemoryError, "failed to allocate memory for context");
110
111
  }
111
112
  rv = rb_hash_new();
112
113
  ctx->rv = &rv;
@@ -119,12 +120,12 @@ cb_bucket_version(int argc, VALUE *argv, VALUE self)
119
120
  exc = cb_check_error(err, "failed to schedule version request", Qnil);
120
121
  cb_params_destroy(&params);
121
122
  if (exc != Qnil) {
122
- xfree(ctx);
123
+ free(ctx);
123
124
  rb_exc_raise(exc);
124
125
  }
125
126
  bucket->nbytes += params.npayload;
126
127
  if (bucket->async) {
127
- maybe_do_loop(bucket);
128
+ cb_maybe_do_loop(bucket);
128
129
  return Qnil;
129
130
  } else {
130
131
  if (ctx->nqueries > 0) {
@@ -132,13 +133,15 @@ cb_bucket_version(int argc, VALUE *argv, VALUE self)
132
133
  lcb_wait(bucket->handle);
133
134
  }
134
135
  exc = ctx->exception;
135
- xfree(ctx);
136
+ free(ctx);
136
137
  if (exc != Qnil) {
137
138
  cb_gc_unprotect(bucket, exc);
138
139
  rb_exc_raise(exc);
139
140
  }
140
- if (bucket->exception != Qnil) {
141
- rb_exc_raise(bucket->exception);
141
+ exc = bucket->exception;
142
+ if (exc != Qnil) {
143
+ bucket->exception = Qnil;
144
+ rb_exc_raise(exc);
142
145
  }
143
146
  return rv;
144
147
  }
@@ -16,7 +16,7 @@ module ActionDispatch
16
16
  # Or remove this file and add following line to your `config/application.rb`:
17
17
  #
18
18
  # require 'action_dispatch/middleware/session/couchbase_store'
19
- # config.session_storage = :couchbase_store
19
+ # config.session_store :couchbase_store
20
20
  #
21
21
  # You can also pass additional options:
22
22
  #
@@ -25,7 +25,7 @@ module ActionDispatch
25
25
  # :expire_after => 5.minutes,
26
26
  # :couchbase => {:bucket => "sessions", :default_format => :marshal}
27
27
  # }
28
- # config.session_storage = :couchbase_store, session_options
28
+ # config.session_store :couchbase_store, session_options
29
29
  #
30
30
  # By default sessions will be serialized to JSON, to allow analyse them
31
31
  # using Map/Reduce.
@@ -184,7 +184,7 @@ module ActiveSupport
184
184
  instrument(:read_multi, names, options) do
185
185
  @data.get(names, options)
186
186
  end
187
- rescue Couchbase::Error::Base => e
187
+ rescue ::Couchbase::Error::Base => e
188
188
  logger.error("#{e.class}: #{e.message}") if logger
189
189
  raise if @raise_errors
190
190
  false
@@ -245,7 +245,7 @@ module ActiveSupport
245
245
  payload[:amount] = amount if payload
246
246
  @data.incr(name, amount, options)
247
247
  end
248
- rescue Couchbase::Error::Base => e
248
+ rescue ::Couchbase::Error::Base => e
249
249
  logger.error("#{e.class}: #{e.message}") if logger
250
250
  raise if @raise_errors
251
251
  false
@@ -277,7 +277,7 @@ module ActiveSupport
277
277
  payload[:amount] = amount if payload
278
278
  @data.decr(name, amount, options)
279
279
  end
280
- rescue Couchbase::Error::Base => e
280
+ rescue ::Couchbase::Error::Base => e
281
281
  logger.error("#{e.class}: #{e.message}") if logger
282
282
  raise if @raise_errors
283
283
  false
@@ -297,7 +297,7 @@ module ActiveSupport
297
297
  # Read an entry from the cache.
298
298
  def read_entry(key, options) # :nodoc:
299
299
  @data.get(key, options)
300
- rescue Couchbase::Error::Base => e
300
+ rescue ::Couchbase::Error::Base => e
301
301
  logger.error("#{e.class}: #{e.message}") if logger
302
302
  raise if @raise_errors
303
303
  nil
@@ -314,7 +314,7 @@ module ActiveSupport
314
314
  options[:ttl] ||= ttl
315
315
  end
316
316
  @data.send(method, key, value, options)
317
- rescue Couchbase::Error::Base => e
317
+ rescue ::Couchbase::Error::Base => e
318
318
  logger.error("#{e.class}: #{e.message}") if logger
319
319
  raise if @raise_errors
320
320
  false
@@ -323,7 +323,7 @@ module ActiveSupport
323
323
  # Delete an entry from the cache.
324
324
  def delete_entry(key, options) # :nodoc:
325
325
  @data.delete(key, options)
326
- rescue Couchbase::Error::Base => e
326
+ rescue ::Couchbase::Error::Base => e
327
327
  logger.error("#{e.class}: #{e.message}") if logger
328
328
  raise if @raise_errors
329
329
  false
data/lib/couchbase.rb CHANGED
@@ -26,6 +26,7 @@ require 'couchbase/bucket'
26
26
  require 'couchbase/view_row'
27
27
  require 'couchbase/view'
28
28
  require 'couchbase/result'
29
+ require 'couchbase/cluster'
29
30
 
30
31
  # Couchbase ruby client
31
32
  module Couchbase
@@ -106,6 +106,9 @@ module Couchbase
106
106
  req.on_body do |body|
107
107
  res = MultiJson.load(body.value)
108
108
  res["rows"].each do |obj|
109
+ if obj['doc']
110
+ obj['doc']['value'] = obj['doc'].delete('json')
111
+ end
109
112
  doc = ViewRow.wrap(self, obj)
110
113
  key = doc.id.sub(/^_design\//, '')
111
114
  next if self.environment == :production && key =~ /dev_/
@@ -117,17 +120,6 @@ module Couchbase
117
120
  async? ? nil : docmap
118
121
  end
119
122
 
120
- # Fetch all documents from the bucket.
121
- #
122
- # @since 1.2.0
123
- #
124
- # @param [Hash] params Params for Couchbase +/_all_docs+ query
125
- #
126
- # @return [Couchbase::View] View object
127
- def all_docs(params = {})
128
- View.new(self, "_all_docs", params)
129
- end
130
-
131
123
  # Update or create design doc with supplied views
132
124
  #
133
125
  # @since 1.2.0
@@ -238,6 +230,9 @@ module Couchbase
238
230
  # end
239
231
  # end
240
232
  def flush
233
+ if !async? && block_given?
234
+ raise ArgumentError, "synchronous mode doesn't support callbacks"
235
+ end
241
236
  req = make_http_request("/pools/default/buckets/#{bucket}/controller/doFlush",
242
237
  :type => :management, :method => :post, :extended => true)
243
238
  res = nil
@@ -0,0 +1,105 @@
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
+ module Couchbase
19
+
20
+ class Cluster
21
+
22
+ # Establish connection to the cluster for administration
23
+ #
24
+ # @param [Hash] options The connection parameter
25
+ # @option options [String] :username The username
26
+ # @option options [String] :password The password
27
+ # @option options [String] :pool ("default") The pool name
28
+ # @option options [String] :hostname ("localhost") The hostname
29
+ # @option options [String] :port (8091) The port
30
+ def initialize(options = {})
31
+ if options[:username].nil? || options[:password].nil?
32
+ raise ArgumentError, "username and password mandatory to connect to the cluster"
33
+ end
34
+ @connection = Bucket.new(options.merge(:type => :cluster))
35
+ end
36
+
37
+ # Create data bucket
38
+ #
39
+ # @param [String] name The name of the bucket
40
+ # @param [Hash] options The bucket parameters
41
+ # @option options [String] :bucket_type ("couchbase") The type of the
42
+ # bucket. Possible values are "memcached" and "couchbase".
43
+ # @option options [Fixnum] :ram_quota (100) The RAM quota in megabytes.
44
+ # @option options [Fixnum] :replica_number (1) The number of replicas of
45
+ # each document
46
+ # @option options [String] :auth_type ("sasl") The authentication type.
47
+ # Possible values are "sasl" and "none". Note you should specify free
48
+ # port for "none"
49
+ # @option options [Fixnum] :proxy_port The port for moxi
50
+ def create_bucket(name, options = {})
51
+ defaults = {
52
+ :type => "couchbase",
53
+ :ram_quota => 100,
54
+ :replica_number => 1,
55
+ :auth_type => "sasl",
56
+ :sasl_password => "",
57
+ :proxy_port => nil
58
+ }
59
+ options = defaults.merge(options)
60
+ params = {"name" => name}
61
+ params["bucketType"] = options[:type]
62
+ params["ramQuotaMB"] = options[:ram_quota]
63
+ params["replicaNumber"] = options[:replica_number]
64
+ params["authType"] = options[:auth_type]
65
+ params["saslPassword"] = options[:sasl_password]
66
+ params["proxyPort"] = options[:proxy_port]
67
+ payload = Utils.encode_params(params.reject!{|k, v| v.nil?})
68
+ request = @connection.make_http_request("/pools/default/buckets",
69
+ :content_type => "application/x-www-form-urlencoded",
70
+ :type => :management,
71
+ :method => :post,
72
+ :extended => true,
73
+ :body => payload)
74
+ response = nil
75
+ request.on_body do |r|
76
+ response = r
77
+ response.instance_variable_set("@operation", :create_bucket)
78
+ yield(response) if block_given?
79
+ end
80
+ request.continue
81
+ response
82
+ end
83
+
84
+ # Delete the data bucket
85
+ #
86
+ # @param [String] name The name of the bucket
87
+ # @param [Hash] options
88
+ def delete_bucket(name, options = {})
89
+ request = @connection.make_http_request("/pools/default/buckets/#{name}",
90
+ :type => :management,
91
+ :method => :delete,
92
+ :extended => true)
93
+ response = nil
94
+ request.on_body do |r|
95
+ response = r
96
+ response.instance_variable_set("@operation", :delete_bucket)
97
+ yield(response) if block_given?
98
+ end
99
+ request.continue
100
+ response
101
+ end
102
+
103
+ end
104
+
105
+ end
@@ -19,11 +19,8 @@ module Couchbase
19
19
 
20
20
  class Utils
21
21
 
22
- def self.build_query(uri, params = nil)
23
- uri = uri.dup
24
- return uri if params.nil? || params.empty?
25
- uri << "?"
26
- uri << params.map do |k, v|
22
+ def self.encode_params(params)
23
+ params.map do |k, v|
27
24
  next if !v && k.to_s == "group"
28
25
  if %w{key keys startkey endkey start_key end_key}.include?(k.to_s)
29
26
  v = MultiJson.dump(v)
@@ -36,6 +33,12 @@ module Couchbase
36
33
  end.compact.join("&")
37
34
  end
38
35
 
36
+ def self.build_query(uri, params = nil)
37
+ uri = uri.dup
38
+ return uri if params.nil? || params.empty?
39
+ uri << "?" << encode_params(params)
40
+ end
41
+
39
42
  def self.escape(s)
40
43
  s.to_s.gsub(/([^ a-zA-Z0-9_.-]+)/nu) {
41
44
  '%'+$1.unpack('H2'*bytesize($1)).join('%').upcase
@@ -17,5 +17,5 @@
17
17
 
18
18
  # Couchbase ruby client
19
19
  module Couchbase
20
- VERSION = "1.2.0.z.beta"
20
+ VERSION = "1.2.1"
21
21
  end
@@ -15,6 +15,8 @@
15
15
  # limitations under the License.
16
16
  #
17
17
 
18
+ require 'base64'
19
+
18
20
  module Couchbase
19
21
 
20
22
  module Error
@@ -27,6 +29,28 @@ module Couchbase
27
29
  super("#{prefix}#{from}: #{reason}")
28
30
  end
29
31
  end
32
+
33
+ class HTTP < Base
34
+ attr_reader :type, :reason
35
+
36
+ def parse_body!
37
+ if @body
38
+ hash = MultiJson.load(@body)
39
+ @type = hash["error"]
40
+ @reason = hash["reason"]
41
+ end
42
+ rescue MultiJson::DecodeError
43
+ @type = @reason = nil
44
+ end
45
+
46
+ def to_s
47
+ str = super
48
+ if @type || @reason
49
+ str.sub(/ \(/, ": #{[@type, @reason].compact.join(": ")} (")
50
+ end
51
+ str
52
+ end
53
+ end
30
54
  end
31
55
 
32
56
  # This class implements Couchbase View execution
@@ -50,7 +74,7 @@ module Couchbase
50
74
  def initialize(bucket, endpoint, params = {})
51
75
  @bucket = bucket
52
76
  @endpoint = endpoint
53
- @params = params
77
+ @params = {:connection_timeout => 75_000}.merge(params)
54
78
  @wrapper_class = params.delete(:wrapper_class) || ViewRow
55
79
  unless @wrapper_class.respond_to?(:wrap)
56
80
  raise ArgumentError, "wrapper class should reposond to :wrap, check the options"
@@ -142,8 +166,14 @@ module Couchbase
142
166
  # the document and store them in {ViewRow#meta} hash.
143
167
  #
144
168
  # @param [Hash] params parameters for Couchbase query.
145
- # @option params [true, false] :include_docs (false) Include the full
146
- # content of the documents in the return.
169
+ # @option params [true, false] :include_docs (false) Include the
170
+ # full content of the documents in the return. Note that the document
171
+ # is fetched from the in memory cache where it may have been changed
172
+ # or even deleted. See also +:quiet+ parameter below to control error
173
+ # reporting during fetch.
174
+ # @option params [true, false] :quiet (true) Do not raise error if
175
+ # associated document not found in the memory. If the parameter +true+
176
+ # will use +nil+ value instead.
147
177
  # @option params [true, false] :descending (false) Return the documents
148
178
  # in descending by key order
149
179
  # @option params [String, Fixnum, Hash, Array] :key Return only
@@ -175,7 +205,7 @@ module Couchbase
175
205
  # response stream.
176
206
  # :stop:: Stop immediately when an error condition occurs. No
177
207
  # further view information will be returned.
178
- # @option params [Fixnum] :connection_timeout (60000) Timeout before the
208
+ # @option params [Fixnum] :connection_timeout (75000) Timeout before the
179
209
  # view request is dropped (milliseconds)
180
210
  # @option params [true, false] :reduce (true) Use the reduction function
181
211
  # @option params [true, false] :group (false) Group the results using
@@ -236,6 +266,11 @@ module Couchbase
236
266
  body = MultiJson.dump(body) unless body.is_a?(String)
237
267
  options.update(:body => body, :method => params.delete(:method) || :post)
238
268
  end
269
+ include_docs = params.delete(:include_docs)
270
+ quiet = true
271
+ if params.has_key?(:quiet)
272
+ quiet = params.delete(:quiet)
273
+ end
239
274
  path = Utils.build_query(@endpoint, params)
240
275
  request = @bucket.make_http_request(path, options)
241
276
  res = []
@@ -260,6 +295,17 @@ module Couchbase
260
295
  raise Error::View.new(from, reason)
261
296
  end
262
297
  else
298
+ if include_docs
299
+ val, flags, cas = @bucket.get(obj['id'], :extended => true, :quiet => quiet)
300
+ obj['doc'] = {
301
+ 'value' => val,
302
+ 'meta' => {
303
+ 'id' => obj['id'],
304
+ 'flags' => flags,
305
+ 'cas' => cas
306
+ }
307
+ }
308
+ end
263
309
  if block_given?
264
310
  yield @wrapper_class.wrap(@bucket, obj)
265
311
  else
@@ -270,7 +316,7 @@ module Couchbase
270
316
  # run event loop until the terminating chunk will be found
271
317
  # last_res variable keeps latest known chunk of the result
272
318
  last_res = nil
273
- loop do
319
+ while true
274
320
  # feed response received chunks to the parser
275
321
  while r = res.shift
276
322
  if r.error