couchbase 1.0.0 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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