couchbase 1.0.0 → 1.1.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.
@@ -20,62 +20,70 @@ ENV['RC_ARCHS'] = '' if RUBY_PLATFORM =~ /darwin/
20
20
 
21
21
  require 'mkmf'
22
22
 
23
- LIBDIR = Config::CONFIG['libdir']
24
- INCLUDEDIR = Config::CONFIG['includedir']
25
-
26
- HEADER_DIRS = [
27
- # First search /opt/local for macports
28
- '/opt/local/include',
29
- # Then search /usr/local for people that installed from source
30
- '/usr/local/include',
31
- # Check the ruby install locations
32
- INCLUDEDIR,
33
- # Finally fall back to /usr
34
- '/usr/include'
35
- ]
23
+ def define(macro, value = nil)
24
+ $defs.push("-D #{[macro.upcase, value].compact.join('=')}")
25
+ end
36
26
 
37
- LIB_DIRS = [
38
- # First search /opt/local for macports
39
- '/opt/local/lib',
40
- # Then search /usr/local for people that installed from source
41
- '/usr/local/lib',
42
- # Check the ruby install locations
43
- LIBDIR,
44
- # Finally fall back to /usr
45
- '/usr/lib'
46
- ]
27
+ $CFLAGS << " #{ENV["CFLAGS"]}"
28
+ $LDFLAGS << " #{ENV["LDFLAGS"]}"
29
+ $LIBS << " #{ENV["LIBS"]}"
47
30
 
48
- # For people using homebrew
49
- brew_prefix = `brew --prefix libevent 2> /dev/null`.chomp
50
- unless brew_prefix.empty?
51
- LIB_DIRS.unshift File.join(brew_prefix, 'lib')
52
- HEADER_DIRS.unshift File.join(brew_prefix, 'include')
31
+ $CFLAGS << ' -std=c99 -Wall -Wextra '
32
+ if ENV['DEBUG']
33
+ $CFLAGS << ' -O0 -ggdb3 -pedantic '
53
34
  end
54
35
 
55
- HEADER_DIRS.delete_if{|d| !File.exists?(d)}
56
- LIB_DIRS.delete_if{|d| !File.exists?(d)}
36
+ if RbConfig::CONFIG['target_os'] =~ /mingw32/
37
+ dir_config("libcouchbase")
38
+ else
39
+ LIBDIR = RbConfig::CONFIG['libdir']
40
+ INCLUDEDIR = RbConfig::CONFIG['includedir']
57
41
 
58
- def define(macro, value = nil)
59
- $defs.push("-D #{[macro.upcase, value].compact.join('=')}")
60
- end
42
+ HEADER_DIRS = [
43
+ # First search /opt/local for macports
44
+ '/opt/local/include',
45
+ # Then search /usr/local for people that installed from source
46
+ '/usr/local/include',
47
+ # Check the ruby install locations
48
+ INCLUDEDIR,
49
+ # Finally fall back to /usr
50
+ '/usr/include'
51
+ ]
61
52
 
62
- # it will find the libcouchbase likely. you can specify its path otherwise
63
- #
64
- # ruby extconf.rb [--with-libcouchbase-include=<dir>] [--with-libcouchbase-lib=<dir>]
65
- #
66
- # or
67
- #
68
- # ruby extconf.rb [--with-libcouchbase-dir=<dir>]
69
- #
70
- dir_config("libcouchbase", HEADER_DIRS, LIB_DIRS)
53
+ LIB_DIRS = [
54
+ # First search /opt/local for macports
55
+ '/opt/local/lib',
56
+ # Then search /usr/local for people that installed from source
57
+ '/usr/local/lib',
58
+ # Check the ruby install locations
59
+ LIBDIR,
60
+ # Finally fall back to /usr
61
+ '/usr/lib'
62
+ ]
71
63
 
72
- if COMMON_HEADERS !~ /"ruby\.h"/
73
- COMMON_HEADERS << %(#include "ruby.h"\n)
64
+ # For people using homebrew
65
+ brew_prefix = `brew --prefix libevent 2> /dev/null`.chomp
66
+ unless brew_prefix.empty?
67
+ LIB_DIRS.unshift File.join(brew_prefix, 'lib')
68
+ HEADER_DIRS.unshift File.join(brew_prefix, 'include')
69
+ end
70
+
71
+ HEADER_DIRS.delete_if{|d| !File.exists?(d)}
72
+ LIB_DIRS.delete_if{|d| !File.exists?(d)}
73
+
74
+ # it will find the libcouchbase likely. you can specify its path otherwise
75
+ #
76
+ # ruby extconf.rb [--with-libcouchbase-include=<dir>] [--with-libcouchbase-lib=<dir>]
77
+ #
78
+ # or
79
+ #
80
+ # ruby extconf.rb [--with-libcouchbase-dir=<dir>]
81
+ #
82
+ dir_config("libcouchbase", HEADER_DIRS, LIB_DIRS)
74
83
  end
75
84
 
76
- $CFLAGS << ' -std=c99 -Wall -Wextra '
77
- if ENV['DEBUG']
78
- $CFLAGS << ' -O0 -ggdb3 -pedantic'
85
+ if COMMON_HEADERS !~ /"ruby\.h"/
86
+ COMMON_HEADERS << %(\n#include "ruby.h"\n)
79
87
  end
80
88
 
81
89
  if try_compile(<<-SRC)
@@ -96,7 +104,10 @@ if try_compile(<<-SRC)
96
104
  define("HAVE_STDARG_PROTOTYPES")
97
105
  end
98
106
 
99
- have_library("event", "event_init", "event.h") || abort
100
- have_library("couchbase", "libcouchbase_create", "libcouchbase/couchbase.h") || abort
107
+
108
+ if RbConfig::CONFIG['target_os'] =~ /mingw32/
109
+ have_library("vbucket", "vbucket_config_create", "libvbucket/vbucket.h") or abort "You should install libvbucket >= 1.8.0.2"
110
+ end
111
+ have_library("couchbase", "libcouchbase_server_versions", "libcouchbase/couchbase.h") or abort "You should install libcouchbase >= 1.0.2"
101
112
  create_header("couchbase_config.h")
102
113
  create_makefile("couchbase_ext")
@@ -17,6 +17,7 @@
17
17
 
18
18
  require 'couchbase/version'
19
19
  require 'yajl/json_gem'
20
+ require 'uri'
20
21
  require 'couchbase_ext'
21
22
  require 'couchbase/bucket'
22
23
 
@@ -24,24 +25,57 @@ require 'couchbase/bucket'
24
25
  module Couchbase
25
26
 
26
27
  class << self
27
- # The method +new+ initializes new Bucket instance with all arguments passed.
28
+ # The method +connect+ initializes new Bucket instance with all arguments passed.
28
29
  #
29
30
  # @example Use default values for all options
30
- # Couchbase.new
31
+ # Couchbase.connect
31
32
  #
32
33
  # @example Establish connection with couchbase default pool and default bucket
33
- # Couchbase.new("http://localhost:8091/pools/default")
34
+ # Couchbase.connect("http://localhost:8091/pools/default")
34
35
  #
35
36
  # @example Select custom bucket
36
- # Couchbase.new("http://localhost:8091/pools/default", :bucket => 'blog')
37
+ # Couchbase.connect("http://localhost:8091/pools/default", :bucket => 'blog')
37
38
  #
38
39
  # @example Specify bucket credentials
39
- # Couchbase.new("http://localhost:8091/pools/default", :bucket => 'blog', :username => 'bucket', :password => 'secret')
40
+ # Couchbase.connect("http://localhost:8091/pools/default", :bucket => 'blog', :username => 'bucket', :password => 'secret')
40
41
  #
41
42
  # @return [Bucket] connection instance
42
- def new(*args)
43
- Bucket.new(*args)
43
+ def connect(*options)
44
+ Bucket.new(*options)
44
45
  end
46
+ alias :new :connect
47
+
48
+ # Default connection options
49
+ #
50
+ # @example Using {Couchbase#connection_options} to change the bucket
51
+ # Couchbase.connection_options = {:bucket => 'blog'}
52
+ # Couchbase.bucket.name #=> "blog"
53
+ #
54
+ # @return [Hash, String]
55
+ attr_accessor :connection_options
56
+
57
+ # @private the thread local storage
58
+ def thread_storage
59
+ Thread.current[:couchbase] ||= {}
60
+ end
61
+
62
+ # The connection instance for current thread
63
+ #
64
+ # @example
65
+ # Couchbase.bucket.set("foo", "bar")
66
+ #
67
+ # @return [Bucket]
68
+ def bucket
69
+ thread_storage[:bucket] ||= connect(*connection_options)
70
+ end
71
+
72
+ # Set a connection instance for current thread
73
+ #
74
+ # @return [Bucket]
75
+ def bucket=(connection)
76
+ thread_storage[:bucket] = connection
77
+ end
78
+
45
79
  end
46
80
 
47
81
  end
@@ -16,17 +16,18 @@
16
16
  #
17
17
 
18
18
  module Couchbase
19
+
19
20
  class Bucket
20
21
 
21
22
  # Reads a key's value from the server and yields it to a block. Replaces
22
23
  # the key's value with the result of the block as long as the key hasn't
23
24
  # been updated in the meantime, otherwise raises
24
- # Couchbase::Error::KeyExists. CAS stands for "compare and swap", and
25
+ # {Couchbase::Error::KeyExists}. CAS stands for "compare and swap", and
25
26
  # avoids the need for manual key mutexing. Read more info here:
26
27
  #
27
28
  # http://docs.couchbase.org/memcached-api/memcached-api-protocol-text_cas.html
28
29
  #
29
- # @param [String] key
30
+ # @param [String, Symbol] key
30
31
  #
31
32
  # @param [Hash] options the options for operation
32
33
  # @option options [String] :ttl (self.default_ttl) the time to live of this key
@@ -37,7 +38,7 @@ module Couchbase
37
38
  # +Result+ object in asynchronous mode.
38
39
  # @yieldreturn [Object] new value.
39
40
  #
40
- # @raise [Couchbase::Errors:KeyExists] if the key was updated before the the
41
+ # @raise [Couchbase::Error::KeyExists] if the key was updated before the the
41
42
  # code in block has been completed (the CAS value has been changed).
42
43
  #
43
44
  # @example Implement append to JSON encoded value
@@ -67,4 +68,5 @@ module Couchbase
67
68
  alias :compare_and_swap :cas
68
69
 
69
70
  end
71
+
70
72
  end
@@ -17,5 +17,5 @@
17
17
 
18
18
  # Couchbase ruby client
19
19
  module Couchbase
20
- VERSION = "1.0.0"
20
+ VERSION = "1.1.0"
21
21
  end
@@ -32,6 +32,16 @@ end
32
32
  # rake compile with_libcouchbase_dir=/opt/couchbase
33
33
  #
34
34
  Rake::ExtensionTask.new("couchbase_ext", gemspec) do |ext|
35
+ ext.cross_compile = true
36
+ ext.cross_platform = [ENV['HOST'] || "i386-mingw32"]
37
+ if ENV['RUBY_CC_VERSION']
38
+ ext.lib_dir = "lib/couchbase"
39
+ end
40
+ ext.cross_compiling do |spec|
41
+ spec.files.delete("lib/couchbase/couchbase_ext.so")
42
+ spec.files.push("lib/couchbase_ext.rb", Dir["lib/couchbase/1.{8,9}/couchbase_ext.so"])
43
+ end
44
+
35
45
  CLEAN.include "#{ext.lib_dir}/*.#{RbConfig::CONFIG['DLEXT']}"
36
46
 
37
47
  ENV.each do |key, val|
@@ -50,3 +60,65 @@ Gem::PackageTask.new(gemspec) do |pkg|
50
60
  pkg.need_zip = true
51
61
  pkg.need_tar = true
52
62
  end
63
+
64
+ require 'mini_portile'
65
+ require 'rake/extensioncompiler'
66
+
67
+ class MiniPortile
68
+ alias :initialize_with_default_host :initialize
69
+ def initialize(name, version)
70
+ initialize_with_default_host(name, version)
71
+ @host = ENV['HOST'] || Rake::ExtensionCompiler.mingw_host
72
+ end
73
+
74
+ alias :cook_without_checkpoint :cook
75
+ def cook
76
+ checkpoint = "ports/.#{name}-#{version}-#{host}.installed"
77
+ unless File.exist?(checkpoint)
78
+ cook_without_checkpoint
79
+ FileUtils.touch(checkpoint)
80
+ end
81
+ end
82
+ end
83
+
84
+ namespace :ports do
85
+ directory "ports"
86
+
87
+ task :libvbucket => ["ports"] do
88
+ recipe = MiniPortile.new "libvbucket", "1.8.0.3"
89
+ recipe.files << "http://packages.couchbase.com/clients/c/#{recipe.name}-#{recipe.version}.tar.gz"
90
+ recipe.configure_options.push("--disable-debug",
91
+ "--without-docs",
92
+ "--disable-dependency-tracking")
93
+ recipe.cook
94
+ recipe.activate
95
+ end
96
+
97
+ task :libcouchbase => [:libvbucket] do
98
+ recipe = MiniPortile.new "libcouchbase", "1.0.2"
99
+ recipe.files << "http://packages.couchbase.com/clients/c/#{recipe.name}-#{recipe.version}.tar.gz"
100
+ recipe.configure_options.push("--disable-debug",
101
+ "--disable-dependency-tracking",
102
+ "--disable-couchbasemock",
103
+ "--disable-tools")
104
+ recipe.cook
105
+ recipe.activate
106
+ end
107
+ end
108
+
109
+ file "lib/couchbase_ext.rb" do
110
+ File.open("lib/couchbase_ext.rb", 'wb') do |f|
111
+ f.write <<-RUBY
112
+ require "couchbase/\#{RUBY_VERSION.sub(/\\.\\d+$/, '')}/couchbase_ext"
113
+ RUBY
114
+ end
115
+ end
116
+
117
+ task :cross => ["lib/couchbase_ext.rb", "ports:libcouchbase"]
118
+
119
+ desc "Package gem for windows"
120
+ task "package:windows" => :package do
121
+ sh("env RUBY_CC_VERSION=1.8.7 rvm 1.8.7 do bundle exec rake cross compile")
122
+ sh("env RUBY_CC_VERSION=1.9.2 rvm 1.9.2 do bundle exec rake cross compile")
123
+ sh("env RUBY_CC_VERSION=1.8.7:1.9.2 rvm 1.9.2 do bundle exec rake cross native gem")
124
+ end
@@ -19,8 +19,8 @@ require 'rake/testtask'
19
19
  require 'rake/clean'
20
20
 
21
21
  rule 'test/CouchbaseMock.jar' do |task|
22
- download_uri = "http://files.couchbase.com/maven2/org/couchbase/mock/CouchbaseMock/0.5-SNAPSHOT/CouchbaseMock-0.5-20120103.162550-11.jar"
23
- sh %{wget -q -O test/CouchbaseMock.jar #{download_uri}}
22
+ jar_path = "0.5-SNAPSHOT/CouchbaseMock-0.5-20120222.060643-15.jar"
23
+ sh %{wget -q -O test/CouchbaseMock.jar http://files.couchbase.com/maven2/org/couchbase/mock/CouchbaseMock/#{jar_path}}
24
24
  end
25
25
 
26
26
  CLOBBER << 'test/CouchbaseMock.jar'
@@ -19,12 +19,52 @@ require 'minitest/autorun'
19
19
  require 'couchbase'
20
20
 
21
21
  require 'socket'
22
+ require 'open-uri'
23
+
24
+ class CouchbaseServer
25
+ attr_accessor :host, :port, :num_nodes, :buckets_spec
26
+
27
+ def real?
28
+ true
29
+ end
30
+
31
+ def initialize(params = {})
32
+ @host, @port = ENV['COUCHBASE_SERVER'].split(':')
33
+ @port = @port.to_i
34
+
35
+ if @host.nil? || @host.empty? || @port == 0
36
+ raise ArgumentError, "Check COUCHBASE_SERVER variable. It should be hostname:port"
37
+ end
38
+
39
+ @config = Yajl::Parser.parse(open("http://#{@host}:#{@port}/pools/default"))
40
+ @num_nodes = @config["nodes"].size
41
+ @buckets_spec = params[:buckets_spec] || "default:" # "default:,protected:secret,cache::memcache"
42
+ end
43
+
44
+ def start
45
+ # flush all buckets
46
+ @buckets_spec.split(',') do |bucket|
47
+ name, password, _ = bucket.split(':')
48
+ connection = Couchbase.new(:hostname => @host,
49
+ :port => @port,
50
+ :username => name,
51
+ :bucket => name,
52
+ :password => password)
53
+ connection.flush
54
+ end
55
+ end
56
+ def stop; end
57
+ end
22
58
 
23
59
  class CouchbaseMock
24
60
  Monitor = Struct.new(:pid, :client, :socket, :port)
25
61
 
26
62
  attr_accessor :host, :port, :buckets_spec, :num_nodes, :num_vbuckets
27
63
 
64
+ def real?
65
+ false
66
+ end
67
+
28
68
  def initialize(params = {})
29
69
  @host = "127.0.0.1"
30
70
  @port = 0
@@ -91,7 +131,17 @@ end
91
131
  class MiniTest::Unit::TestCase
92
132
 
93
133
  def start_mock(params = {})
94
- mock = CouchbaseMock.new(params)
134
+ mock = nil
135
+ if ENV['COUCHBASE_SERVER']
136
+ mock = CouchbaseServer.new(params)
137
+ if (params[:port] && mock.port != params[:port]) ||
138
+ (params[:host] && mock.host != params[:host]) ||
139
+ mock.buckets_spec != "default:"
140
+ skip("Unable to configure real cluster. Requested config is: #{params.inspect}")
141
+ end
142
+ else
143
+ mock = CouchbaseMock.new(params)
144
+ end
95
145
  mock.start
96
146
  mock
97
147
  end
@@ -111,7 +161,7 @@ class MiniTest::Unit::TestCase
111
161
  stop_mock(mock) if mock
112
162
  end
113
163
 
114
- def test_id(*suffixes)
164
+ def uniq_id(*suffixes)
115
165
  [caller.first[/.*[` ](.*)'/, 1], suffixes].join("_")
116
166
  end
117
167
  end
@@ -28,70 +28,81 @@ class TestArithmetic < MiniTest::Unit::TestCase
28
28
  end
29
29
 
30
30
  def test_trivial_incr_decr
31
- connection = Couchbase.new(:port => @mock.port)
31
+ connection = Couchbase.new(:hostname => @mock.host, :port => @mock.port)
32
32
 
33
- connection.set(test_id, 1)
34
- val = connection.incr(test_id)
33
+ connection.set(uniq_id, 1)
34
+ val = connection.incr(uniq_id)
35
35
  assert_equal 2, val
36
- val = connection.get(test_id)
36
+ val = connection.get(uniq_id)
37
37
  assert_equal 2, val
38
38
 
39
- connection.set(test_id, 7)
40
- val = connection.decr(test_id)
39
+ connection.set(uniq_id, 7)
40
+ val = connection.decr(uniq_id)
41
41
  assert_equal 6, val
42
- val = connection.get(test_id)
42
+ val = connection.get(uniq_id)
43
43
  assert_equal 6, val
44
44
  end
45
45
 
46
46
  def test_it_fails_to_incr_decr_missing_key
47
- connection = Couchbase.new(:port => @mock.port)
47
+ connection = Couchbase.new(:hostname => @mock.host, :port => @mock.port)
48
48
 
49
49
  assert_raises(Couchbase::Error::NotFound) do
50
- connection.incr(test_id(:missing))
50
+ connection.incr(uniq_id(:missing))
51
51
  end
52
52
  assert_raises(Couchbase::Error::NotFound) do
53
- connection.decr(test_id(:missing))
53
+ connection.decr(uniq_id(:missing))
54
54
  end
55
55
  end
56
56
 
57
57
  def test_it_creates_missing_key_when_initial_value_specified
58
- connection = Couchbase.new(:port => @mock.port)
58
+ connection = Couchbase.new(:hostname => @mock.host, :port => @mock.port)
59
59
 
60
- val = connection.incr(test_id(:missing), :initial => 5)
60
+ val = connection.incr(uniq_id(:missing), :initial => 5)
61
61
  assert_equal 5, val
62
- val = connection.incr(test_id(:missing), :initial => 5)
62
+ val = connection.incr(uniq_id(:missing), :initial => 5)
63
63
  assert_equal 6, val
64
- val = connection.get(test_id(:missing))
64
+ val = connection.get(uniq_id(:missing))
65
65
  assert_equal 6, val
66
66
  end
67
67
 
68
68
  def test_it_uses_zero_as_default_value_for_missing_keys
69
- connection = Couchbase.new(:port => @mock.port)
69
+ connection = Couchbase.new(:hostname => @mock.host, :port => @mock.port)
70
70
 
71
- val = connection.incr(test_id(:missing), :create => true)
71
+ val = connection.incr(uniq_id(:missing), :create => true)
72
72
  assert_equal 0, val
73
- val = connection.incr(test_id(:missing), :create => true)
73
+ val = connection.incr(uniq_id(:missing), :create => true)
74
74
  assert_equal 1, val
75
- val = connection.get(test_id(:missing))
75
+ val = connection.get(uniq_id(:missing))
76
76
  assert_equal 1, val
77
77
  end
78
78
 
79
79
  def test_it_allows_custom_ttl
80
- connection = Couchbase.new(:port => @mock.port)
80
+ connection = Couchbase.new(:hostname => @mock.host, :port => @mock.port)
81
81
 
82
- val = connection.incr(test_id(:missing), :create => true, :ttl => 1)
82
+ val = connection.incr(uniq_id(:missing), :create => true, :ttl => 1)
83
83
  assert_equal 0, val
84
- val = connection.incr(test_id(:missing), :create => true)
84
+ val = connection.incr(uniq_id(:missing), :create => true)
85
85
  assert_equal 1, val
86
- sleep(1)
87
- refute connection.get(test_id(:missing))
86
+ sleep(2)
87
+ refute connection.get(uniq_id(:missing))
88
+ end
89
+
90
+ def test_decrement_with_absolute_ttl
91
+ connection = Couchbase.new(:hostname => @mock.host, :port => @mock.port)
92
+ # absolute TTL: one second from now
93
+ exp = Time.now.to_i + 1
94
+ val = connection.decr(uniq_id, 12, :initial => 0, :ttl => exp)
95
+ assert_equal 0, val
96
+ assert_equal 0, connection.get(uniq_id)
97
+ sleep(2)
98
+ refute connection.get(uniq_id)
88
99
  end
89
100
 
90
101
  def test_it_allows_custom_delta
91
- connection = Couchbase.new(:port => @mock.port)
102
+ connection = Couchbase.new(:hostname => @mock.host, :port => @mock.port)
92
103
 
93
- connection.set(test_id, 12)
94
- val = connection.incr(test_id, 10)
104
+ connection.set(uniq_id, 12)
105
+ val = connection.incr(uniq_id, 10)
95
106
  assert_equal 22, val
96
107
  end
97
108