libcouchbase-mapo 1.4.1
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.
- checksums.yaml +7 -0
- data/.gitignore +20 -0
- data/.gitmodules +3 -0
- data/.rspec +1 -0
- data/.travis.yml +38 -0
- data/Gemfile +4 -0
- data/LICENSE +24 -0
- data/README.md +445 -0
- data/Rakefile +76 -0
- data/ext/README.md +6 -0
- data/ext/Rakefile +19 -0
- data/lib/libcouchbase.rb +40 -0
- data/lib/libcouchbase/bucket.rb +825 -0
- data/lib/libcouchbase/callbacks.rb +69 -0
- data/lib/libcouchbase/connection.rb +886 -0
- data/lib/libcouchbase/design_docs.rb +92 -0
- data/lib/libcouchbase/error.rb +68 -0
- data/lib/libcouchbase/ext/libcouchbase.rb +1175 -0
- data/lib/libcouchbase/ext/libcouchbase/cmdbase.rb +23 -0
- data/lib/libcouchbase/ext/libcouchbase/cmdcounter.rb +36 -0
- data/lib/libcouchbase/ext/libcouchbase/cmdendure.rb +26 -0
- data/lib/libcouchbase/ext/libcouchbase/cmdfts.rb +24 -0
- data/lib/libcouchbase/ext/libcouchbase/cmdget.rb +30 -0
- data/lib/libcouchbase/ext/libcouchbase/cmdgetreplica.rb +49 -0
- data/lib/libcouchbase/ext/libcouchbase/cmdhttp.rb +58 -0
- data/lib/libcouchbase/ext/libcouchbase/cmdn1ql.rb +40 -0
- data/lib/libcouchbase/ext/libcouchbase/cmdobseqno.rb +33 -0
- data/lib/libcouchbase/ext/libcouchbase/cmdobserve.rb +30 -0
- data/lib/libcouchbase/ext/libcouchbase/cmdstore.rb +40 -0
- data/lib/libcouchbase/ext/libcouchbase/cmdstoredur.rb +45 -0
- data/lib/libcouchbase/ext/libcouchbase/cmdsubdoc.rb +61 -0
- data/lib/libcouchbase/ext/libcouchbase/cmdverbosity.rb +29 -0
- data/lib/libcouchbase/ext/libcouchbase/cmdviewquery.rb +61 -0
- data/lib/libcouchbase/ext/libcouchbase/contigbuf.rb +14 -0
- data/lib/libcouchbase/ext/libcouchbase/create_st.rb +15 -0
- data/lib/libcouchbase/ext/libcouchbase/create_st0.rb +23 -0
- data/lib/libcouchbase/ext/libcouchbase/create_st1.rb +26 -0
- data/lib/libcouchbase/ext/libcouchbase/create_st2.rb +32 -0
- data/lib/libcouchbase/ext/libcouchbase/create_st3.rb +26 -0
- data/lib/libcouchbase/ext/libcouchbase/crst_u.rb +20 -0
- data/lib/libcouchbase/ext/libcouchbase/durability_opts_st_v.rb +11 -0
- data/lib/libcouchbase/ext/libcouchbase/durability_opts_t.rb +14 -0
- data/lib/libcouchbase/ext/libcouchbase/durabilityopt_sv0.rb +63 -0
- data/lib/libcouchbase/ext/libcouchbase/enums.rb +1007 -0
- data/lib/libcouchbase/ext/libcouchbase/fragbuf.rb +18 -0
- data/lib/libcouchbase/ext/libcouchbase/ftshandle.rb +7 -0
- data/lib/libcouchbase/ext/libcouchbase/histogram.rb +34 -0
- data/lib/libcouchbase/ext/libcouchbase/http_request_t.rb +7 -0
- data/lib/libcouchbase/ext/libcouchbase/keybuf.rb +20 -0
- data/lib/libcouchbase/ext/libcouchbase/multicmd_ctx.rb +30 -0
- data/lib/libcouchbase/ext/libcouchbase/mutation_token.rb +17 -0
- data/lib/libcouchbase/ext/libcouchbase/n1qlhandle.rb +7 -0
- data/lib/libcouchbase/ext/libcouchbase/n1qlparams.rb +7 -0
- data/lib/libcouchbase/ext/libcouchbase/respbase.rb +29 -0
- data/lib/libcouchbase/ext/libcouchbase/respcounter.rb +32 -0
- data/lib/libcouchbase/ext/libcouchbase/respendure.rb +49 -0
- data/lib/libcouchbase/ext/libcouchbase/respfts.rb +40 -0
- data/lib/libcouchbase/ext/libcouchbase/respget.rb +44 -0
- data/lib/libcouchbase/ext/libcouchbase/resphttp.rb +48 -0
- data/lib/libcouchbase/ext/libcouchbase/respmcversion.rb +38 -0
- data/lib/libcouchbase/ext/libcouchbase/respn1ql.rb +41 -0
- data/lib/libcouchbase/ext/libcouchbase/respobseqno.rb +52 -0
- data/lib/libcouchbase/ext/libcouchbase/respobserve.rb +41 -0
- data/lib/libcouchbase/ext/libcouchbase/respserverbase.rb +32 -0
- data/lib/libcouchbase/ext/libcouchbase/respstats.rb +38 -0
- data/lib/libcouchbase/ext/libcouchbase/respstore.rb +32 -0
- data/lib/libcouchbase/ext/libcouchbase/respstoredur.rb +38 -0
- data/lib/libcouchbase/ext/libcouchbase/respsubdoc.rb +35 -0
- data/lib/libcouchbase/ext/libcouchbase/respviewquery.rb +67 -0
- data/lib/libcouchbase/ext/libcouchbase/sdentry.rb +22 -0
- data/lib/libcouchbase/ext/libcouchbase/sdspec.rb +31 -0
- data/lib/libcouchbase/ext/libcouchbase/t.rb +7 -0
- data/lib/libcouchbase/ext/libcouchbase/valbuf.rb +22 -0
- data/lib/libcouchbase/ext/libcouchbase/valbuf_u_buf.rb +14 -0
- data/lib/libcouchbase/ext/libcouchbase/viewhandle.rb +7 -0
- data/lib/libcouchbase/ext/libcouchbase_libuv.rb +22 -0
- data/lib/libcouchbase/ext/tasks.rb +39 -0
- data/lib/libcouchbase/n1ql.rb +78 -0
- data/lib/libcouchbase/query_full_text.rb +147 -0
- data/lib/libcouchbase/query_n1ql.rb +123 -0
- data/lib/libcouchbase/query_view.rb +135 -0
- data/lib/libcouchbase/results_fiber.rb +281 -0
- data/lib/libcouchbase/results_native.rb +220 -0
- data/lib/libcouchbase/subdoc_request.rb +139 -0
- data/lib/libcouchbase/version.rb +5 -0
- data/libcouchbase.gemspec +68 -0
- data/spec/bucket_spec.rb +290 -0
- data/spec/connection_spec.rb +257 -0
- data/spec/design_docs_spec.rb +31 -0
- data/spec/error_spec.rb +26 -0
- data/spec/fts_spec.rb +135 -0
- data/spec/n1ql_spec.rb +206 -0
- data/spec/results_libuv_spec.rb +244 -0
- data/spec/results_native_spec.rb +259 -0
- data/spec/seed/2016-10-25T043505Z/2016-10-25T043505Z-full/bucket-default/design.json +1 -0
- data/spec/seed/2016-10-25T043505Z/2016-10-25T043505Z-full/bucket-default/node-127.0.0.1%3A8091/data-0000.cbb +0 -0
- data/spec/seed/2016-10-25T043505Z/2016-10-25T043505Z-full/bucket-default/node-127.0.0.1%3A8091/failover.json +1 -0
- data/spec/seed/2016-10-25T043505Z/2016-10-25T043505Z-full/bucket-default/node-127.0.0.1%3A8091/meta.json +1 -0
- data/spec/seed/2016-10-25T043505Z/2016-10-25T043505Z-full/bucket-default/node-127.0.0.1%3A8091/seqno.json +1 -0
- data/spec/seed/2016-10-25T043505Z/2016-10-25T043505Z-full/bucket-default/node-127.0.0.1%3A8091/snapshot_markers.json +1 -0
- data/spec/subdoc_spec.rb +192 -0
- data/spec/view_spec.rb +201 -0
- data/windows_build.md +36 -0
- metadata +265 -0
data/Rakefile
ADDED
@@ -0,0 +1,76 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rspec/core/rake_task' # testing framework
|
3
|
+
require 'yard' # yard documentation
|
4
|
+
require 'ffi' # loads the extension
|
5
|
+
require 'rake/clean' # for the :clobber rake task
|
6
|
+
require File.expand_path('../lib/libcouchbase/ext/tasks', __FILE__) # platform specific rake tasks used by compile
|
7
|
+
|
8
|
+
|
9
|
+
|
10
|
+
# By default we don't run network tests
|
11
|
+
task :default => :limited_spec
|
12
|
+
RSpec::Core::RakeTask.new(:limited_spec) do |t|
|
13
|
+
# Exclude full text search tests until we can automate index creation
|
14
|
+
t.rspec_opts = "--tag ~full_text_search --tag ~n1ql_query --fail-fast"
|
15
|
+
end
|
16
|
+
RSpec::Core::RakeTask.new(:spec)
|
17
|
+
|
18
|
+
|
19
|
+
desc 'Run all tests'
|
20
|
+
task :test => [:spec]
|
21
|
+
|
22
|
+
|
23
|
+
YARD::Rake::YardocTask.new do |t|
|
24
|
+
t.files = ['lib/**/*.rb', '-', 'ext/README.md', 'README.md']
|
25
|
+
end
|
26
|
+
|
27
|
+
|
28
|
+
desc 'Compile libcouchbase from submodule'
|
29
|
+
if FFI::Platform.windows?
|
30
|
+
task :compile do
|
31
|
+
puts "See windows_build.md for build instructions"
|
32
|
+
end
|
33
|
+
else
|
34
|
+
task :compile => ["ext/libcouchbase/build/lib/libcouchbase_libuv.#{FFI::Platform::LIBSUFFIX}"]
|
35
|
+
CLOBBER.include("ext/libcouchbase/build/lib/libcouchbase_libuv.#{FFI::Platform::LIBSUFFIX}")
|
36
|
+
end
|
37
|
+
|
38
|
+
|
39
|
+
# NOTE:: Generated on OSX
|
40
|
+
desc 'Generate the FFI bindings'
|
41
|
+
task :generate_bindings do
|
42
|
+
require "ffi/gen"
|
43
|
+
|
44
|
+
# NOTE:: you must export the include dir:
|
45
|
+
# export CPATH=./ext/libcouchbase/include/
|
46
|
+
#
|
47
|
+
# Once generated we need to:
|
48
|
+
# * adjust the ffi_lib path:
|
49
|
+
# ffi_lib ::File.expand_path("../../../../ext/libcouchbase/build/lib/libcouchbase_libuv.#{FFI::Platform::LIBSUFFIX}", __FILE__)
|
50
|
+
# * Rename some structs strings to pointers
|
51
|
+
# create_st3.rb -> connstr, username, passwd
|
52
|
+
# cmdhttp.rb -> body, reqhandle, content_type, username, password, host
|
53
|
+
# cmdfts.rb -> query
|
54
|
+
# respfts.rb -> row
|
55
|
+
# respviewquery.rb -> value, geometry, docid
|
56
|
+
# respn1ql.rb -> row
|
57
|
+
|
58
|
+
FFI::Gen.generate(
|
59
|
+
module_name: "Libcouchbase::Ext",
|
60
|
+
ffi_lib: "libcouchbase",
|
61
|
+
require_path: "libcouchbase/ext/libcouchbase",
|
62
|
+
headers: [
|
63
|
+
"./ext/libcouchbase/include/libcouchbase/couchbase.h",
|
64
|
+
"./ext/libcouchbase/include/libcouchbase/error.h",
|
65
|
+
"./ext/libcouchbase/include/libcouchbase/views.h",
|
66
|
+
"./ext/libcouchbase/include/libcouchbase/subdoc.h",
|
67
|
+
"./ext/libcouchbase/include/libcouchbase/n1ql.h",
|
68
|
+
"./ext/libcouchbase/include/libcouchbase/cbft.h",
|
69
|
+
"./ext/libcouchbase/include/libcouchbase/kvbuf.h"
|
70
|
+
],
|
71
|
+
# Searching for stdarg.h
|
72
|
+
cflags: ["-I/System/Library/Frameworks/Kernel.framework/Versions/A/Headers"],
|
73
|
+
prefixes: ["LCB_", "lcb_"],
|
74
|
+
output: "libcouchbase.rb"
|
75
|
+
)
|
76
|
+
end
|
data/ext/README.md
ADDED
data/ext/Rakefile
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
|
2
|
+
require 'rubygems'
|
3
|
+
require 'ffi'
|
4
|
+
require 'rake/clean'
|
5
|
+
require File.expand_path('../../lib/libcouchbase/ext/tasks', __FILE__)
|
6
|
+
|
7
|
+
|
8
|
+
Dir.chdir File.expand_path("../../", __FILE__)
|
9
|
+
|
10
|
+
|
11
|
+
task :default => :libcouchbase
|
12
|
+
|
13
|
+
desc "Compile libcouchbase from submodule"
|
14
|
+
if FFI::Platform.windows?
|
15
|
+
task :libcouchbase do; end
|
16
|
+
else
|
17
|
+
task :libcouchbase => ["ext/libcouchbase/build/lib/libcouchbase_libuv.#{FFI::Platform::LIBSUFFIX}"]
|
18
|
+
CLOBBER.include("ext/libcouchbase/build/lib/libcouchbase_libuv.#{FFI::Platform::LIBSUFFIX}")
|
19
|
+
end
|
data/lib/libcouchbase.rb
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
# frozen_string_literal: true, encoding: ASCII-8BIT
|
2
|
+
|
3
|
+
require 'libuv'
|
4
|
+
|
5
|
+
module Libcouchbase
|
6
|
+
require 'libcouchbase/ext/libcouchbase'
|
7
|
+
require 'libcouchbase/error'
|
8
|
+
require 'libcouchbase/callbacks'
|
9
|
+
require 'libcouchbase/connection'
|
10
|
+
|
11
|
+
DefaultOpts = Struct.new(:host, :bucket, :username, :password)
|
12
|
+
Defaults = DefaultOpts.new('127.0.0.1', 'default')
|
13
|
+
|
14
|
+
class Results
|
15
|
+
include Enumerable
|
16
|
+
|
17
|
+
# streams results as they are returned from the database
|
18
|
+
#
|
19
|
+
# unlike other operations, such as each, the results are not stored
|
20
|
+
# for later use and are discarded as soon as possible to save memory
|
21
|
+
#
|
22
|
+
# @yieldparam [Object] value the value of the current row
|
23
|
+
def stream; end
|
24
|
+
|
25
|
+
attr_reader :complete_result_set, :query_in_progress
|
26
|
+
attr_reader :query_completed, :metadata
|
27
|
+
end
|
28
|
+
|
29
|
+
autoload :N1QL, 'libcouchbase/n1ql'
|
30
|
+
autoload :Bucket, 'libcouchbase/bucket'
|
31
|
+
autoload :QueryView, 'libcouchbase/query_view'
|
32
|
+
autoload :QueryN1QL, 'libcouchbase/query_n1ql'
|
33
|
+
autoload :QueryFullText, 'libcouchbase/query_full_text'
|
34
|
+
autoload :DesignDoc, 'libcouchbase/design_docs'
|
35
|
+
autoload :DesignDocs, 'libcouchbase/design_docs'
|
36
|
+
autoload :ResultsEM, 'libcouchbase/results_fiber'
|
37
|
+
autoload :ResultsLibuv, 'libcouchbase/results_fiber'
|
38
|
+
autoload :ResultsNative, 'libcouchbase/results_native'
|
39
|
+
autoload :SubdocRequest, 'libcouchbase/subdoc_request'
|
40
|
+
end
|
@@ -0,0 +1,825 @@
|
|
1
|
+
# frozen_string_literal: true, encoding: ASCII-8BIT
|
2
|
+
|
3
|
+
require 'forwardable'
|
4
|
+
require 'thread'
|
5
|
+
|
6
|
+
|
7
|
+
module Libcouchbase
|
8
|
+
class Bucket
|
9
|
+
extend Forwardable
|
10
|
+
|
11
|
+
# Finalizer done right
|
12
|
+
# http://www.mikeperham.com/2010/02/24/the-trouble-with-ruby-finalizers/
|
13
|
+
def self.finalize(connection)
|
14
|
+
proc {
|
15
|
+
connection.destroy.finally do
|
16
|
+
connection.reactor.unref
|
17
|
+
end
|
18
|
+
}
|
19
|
+
end
|
20
|
+
|
21
|
+
def initialize(**options)
|
22
|
+
@connection_options = options
|
23
|
+
@connection = Connection.new(**options)
|
24
|
+
connect
|
25
|
+
|
26
|
+
# This obtains the connections reactor
|
27
|
+
@reactor = reactor
|
28
|
+
@quiet = false
|
29
|
+
|
30
|
+
# clean up the connection once this object is garbage collected
|
31
|
+
ObjectSpace.define_finalizer( self, self.class.finalize(@connection) )
|
32
|
+
end
|
33
|
+
|
34
|
+
|
35
|
+
attr_reader :connection
|
36
|
+
attr_accessor :quiet
|
37
|
+
def_delegators :@connection, :bucket, :reactor
|
38
|
+
|
39
|
+
|
40
|
+
# Obtain an object stored in Couchbase by given key.
|
41
|
+
#
|
42
|
+
# @param keys [String, Symbol, Array] One or several keys to fetch
|
43
|
+
# @param options [Hash] Options for operation.
|
44
|
+
# @option options [Integer] :lock time to lock this key for. Max time 30s
|
45
|
+
# @option options [true, false] :extended (false) If set to +true+, the
|
46
|
+
# operation will return a +Libcouchbase::Result+, otherwise (by default)
|
47
|
+
# it returns just the value.
|
48
|
+
# @option options [true, false] :quiet (self.quiet) If set to +true+, the
|
49
|
+
# operation won't raise error for missing key, it will return +nil+.
|
50
|
+
# Otherwise it will raise a not found error.
|
51
|
+
# @option options [true, false] :assemble_hash (false) Assemble Hash for
|
52
|
+
# results.
|
53
|
+
#
|
54
|
+
# @return [Object, Array, Hash, Libcouchbase::Result] the value(s)
|
55
|
+
#
|
56
|
+
# @raise [Libcouchbase::Error::KeyExists] if the key already exists on the server
|
57
|
+
# with a different CAS value to that provided
|
58
|
+
# @raise [Libouchbase::Error::Timedout] if timeout interval for observe exceeds
|
59
|
+
# @raise [Libouchbase::Error::NetworkError] if there was a communication issue
|
60
|
+
# @raise [Libcouchbase::Error::KeyNotFound] if the key doesn't exists
|
61
|
+
#
|
62
|
+
# @example Get single value in quiet mode (the default)
|
63
|
+
# c.get("foo") #=> the associated value or nil
|
64
|
+
#
|
65
|
+
# @example Use alternative hash-like syntax
|
66
|
+
# c["foo"] #=> the associated value or nil
|
67
|
+
#
|
68
|
+
# @example Get single value in verbose mode
|
69
|
+
# c.get("missing-foo", quiet: false) #=> raises Libcouchbase::Error::NotFound
|
70
|
+
#
|
71
|
+
# @example Get multiple keys
|
72
|
+
# c.get("foo", "bar", "baz") #=> [val1, val2, val3]
|
73
|
+
#
|
74
|
+
# @example Get multiple keys with assembing result into the Hash
|
75
|
+
# c.get("foo", "bar", "baz", assemble_hash: true)
|
76
|
+
# #=> {"foo" => val1, "bar" => val2, "baz" => val3}
|
77
|
+
#
|
78
|
+
# @example Get and lock key using default timeout
|
79
|
+
# c.get("foo", lock: true) # This locks for the maximum time of 30 seconds
|
80
|
+
#
|
81
|
+
# @example Get and lock key using custom timeout
|
82
|
+
# c.get("foo", lock: 3)
|
83
|
+
#
|
84
|
+
# @example Get and lock multiple keys using custom timeout
|
85
|
+
# c.get("foo", "bar", lock: 3)
|
86
|
+
def get(key, *keys, extended: false, async: false, quiet: @quiet, assemble_hash: false, **opts)
|
87
|
+
was_array = key.respond_to?(:to_a) || keys.length > 0
|
88
|
+
keys.unshift Array(key) # Convert enumerables
|
89
|
+
keys.flatten! # Ensure we're left with a list of keys
|
90
|
+
|
91
|
+
if keys.length == 1
|
92
|
+
promise = @connection.get(keys[0], **opts)
|
93
|
+
|
94
|
+
unless extended
|
95
|
+
promise = promise.then(proc { |resp|
|
96
|
+
resp.value
|
97
|
+
})
|
98
|
+
end
|
99
|
+
|
100
|
+
if quiet
|
101
|
+
promise = promise.catch { |err|
|
102
|
+
if err.is_a? Libcouchbase::Error::KeyNotFound
|
103
|
+
nil
|
104
|
+
else
|
105
|
+
::Libuv::Q.reject(@reactor, err)
|
106
|
+
end
|
107
|
+
}
|
108
|
+
end
|
109
|
+
|
110
|
+
if assemble_hash
|
111
|
+
promise = promise.then(proc { |val|
|
112
|
+
hash = defined?(::HashWithIndifferentAccess) ? ::HashWithIndifferentAccess.new : {}
|
113
|
+
hash[keys[0]] = val
|
114
|
+
hash
|
115
|
+
})
|
116
|
+
elsif was_array
|
117
|
+
promise = promise.then(proc { |val|
|
118
|
+
Array(val)
|
119
|
+
})
|
120
|
+
end
|
121
|
+
|
122
|
+
result(promise, async)
|
123
|
+
else
|
124
|
+
promises = keys.collect { |key|
|
125
|
+
@connection.get(key, **opts)
|
126
|
+
}
|
127
|
+
|
128
|
+
if quiet
|
129
|
+
promises.map! { |prom|
|
130
|
+
prom.catch { |err|
|
131
|
+
if err.is_a? Libcouchbase::Error::KeyNotFound
|
132
|
+
nil
|
133
|
+
else
|
134
|
+
::Libuv::Q.reject(@reactor, err)
|
135
|
+
end
|
136
|
+
}
|
137
|
+
}
|
138
|
+
end
|
139
|
+
|
140
|
+
result(@reactor.all(*promises).then(proc { |results|
|
141
|
+
if extended
|
142
|
+
results.compact!
|
143
|
+
else
|
144
|
+
results.collect! { |resp| resp.value if resp }
|
145
|
+
end
|
146
|
+
|
147
|
+
if assemble_hash
|
148
|
+
hash = defined?(::HashWithIndifferentAccess) ? ::HashWithIndifferentAccess.new : {}
|
149
|
+
keys.each_with_index do |key, index|
|
150
|
+
hash[key] = results[index]
|
151
|
+
end
|
152
|
+
hash
|
153
|
+
else
|
154
|
+
results
|
155
|
+
end
|
156
|
+
}), async)
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
# Quietly obtain an object stored in Couchbase by given key.
|
161
|
+
def [](key)
|
162
|
+
get(key, quiet: true)
|
163
|
+
end
|
164
|
+
|
165
|
+
# A helper method for returning a default value if one doesn't exist for the key
|
166
|
+
def fetch(key, value = nil, async: false, **opts)
|
167
|
+
cached_obj = get(key, quiet: true, async: false, extended: false)
|
168
|
+
return cached_obj if cached_obj
|
169
|
+
value = value || yield
|
170
|
+
set(key, value, opts.merge(async: false, extended: false))
|
171
|
+
value
|
172
|
+
end
|
173
|
+
|
174
|
+
# Add the item to the database, but fail if the object exists already
|
175
|
+
#
|
176
|
+
# @param key [String, Symbol] Key used to reference the value.
|
177
|
+
# @param value [Object] Value to be stored
|
178
|
+
# @param options [Hash] Options for operation.
|
179
|
+
# @option options [Integer] :ttl Expiry time for key in seconds
|
180
|
+
# @option options [Integer] :expire_in Expiry time for key in seconds
|
181
|
+
# @option options [Integer, Time] :expire_at Unix epoc or time at which a key
|
182
|
+
# should expire
|
183
|
+
# @option options [Integer] :cas The CAS value for an object. This value is
|
184
|
+
# created on the server and is guaranteed to be unique for each value of
|
185
|
+
# a given key. This value is used to provide simple optimistic
|
186
|
+
# concurrency control when multiple clients or threads try to update an
|
187
|
+
# item simultaneously.
|
188
|
+
# @option options [Integer] :persist_to persist to a number of nodes before returing
|
189
|
+
# a result. Use -1 to persist to the maximum number of nodes
|
190
|
+
# @option options [Integer] :replicate_to replicate to a number of nodes before
|
191
|
+
# returning a result. Use -1 to replicate to the maximum number of nodes
|
192
|
+
#
|
193
|
+
# @return [Libcouchbase::Result] this includes the CAS value of the object.
|
194
|
+
#
|
195
|
+
# @raise [Libcouchbase::Error::KeyExists] if the key already exists on the server
|
196
|
+
# @raise [Libouchbase::Error::Timedout] if timeout interval for observe exceeds
|
197
|
+
# @raise [Libouchbase::Error::NetworkError] if there was a communication issue
|
198
|
+
#
|
199
|
+
# @example Store the key which will be expired in 2 seconds using relative TTL.
|
200
|
+
# c.add("foo", "bar", expire_in: 2)
|
201
|
+
#
|
202
|
+
# @example Store the key which will be expired in 2 seconds using absolute TTL.
|
203
|
+
# c.add(:foo, :bar, expire_at: Time.now.to_i + 2)
|
204
|
+
#
|
205
|
+
# @example Set application specific flags
|
206
|
+
# c.add("foo", "bar", flags: 0x1000)
|
207
|
+
#
|
208
|
+
# @example Ensure that the key will be persisted at least on the one node
|
209
|
+
# c.add("foo", "bar", persist_to: 1)
|
210
|
+
def add(key, value, async: false, **opts)
|
211
|
+
result @connection.store(key, value, **AddDefaults.merge(opts)), async
|
212
|
+
end
|
213
|
+
AddDefaults = {operation: :add}.freeze
|
214
|
+
|
215
|
+
# Unconditionally store the object in the Couchbase
|
216
|
+
#
|
217
|
+
# @param key [String, Symbol] Key used to reference the value.
|
218
|
+
# @param value [Object] Value to be stored
|
219
|
+
# @param options [Hash] Options for operation.
|
220
|
+
# @option options [Integer] :ttl Expiry time for key in seconds
|
221
|
+
# @option options [Integer] :expire_in Expiry time for key in seconds
|
222
|
+
# @option options [Integer, Time] :expire_at Unix epoc or time at which a key
|
223
|
+
# should expire
|
224
|
+
# @option options [Integer] :cas The CAS value for an object. This value is
|
225
|
+
# created on the server and is guaranteed to be unique for each value of
|
226
|
+
# a given key. This value is used to provide simple optimistic
|
227
|
+
# concurrency control when multiple clients or threads try to update an
|
228
|
+
# item simultaneously.
|
229
|
+
# @option options [Integer] :persist_to persist to a number of nodes before returing
|
230
|
+
# a result. Use -1 to persist to the maximum number of nodes
|
231
|
+
# @option options [Integer] :replicate_to replicate to a number of nodes before
|
232
|
+
# returning a result. Use -1 to replicate to the maximum number of nodes
|
233
|
+
#
|
234
|
+
# @return [Libcouchbase::Result] this includes the CAS value of the object.
|
235
|
+
#
|
236
|
+
# @raise [Libcouchbase::Error::KeyExists] if the key already exists on the server
|
237
|
+
# with a different CAS value to that provided
|
238
|
+
# @raise [Libouchbase::Error::Timedout] if timeout interval for observe exceeds
|
239
|
+
# @raise [Libouchbase::Error::NetworkError] if there was a communication issue
|
240
|
+
#
|
241
|
+
# @example Store the key which will be expired in 2 seconds using relative TTL.
|
242
|
+
# c.set("foo", "bar", expire_in: 2)
|
243
|
+
#
|
244
|
+
# @example Store the key which will be expired in 2 seconds using absolute TTL.
|
245
|
+
# c.set(:foo, :bar, expire_at: Time.now.to_i + 2)
|
246
|
+
#
|
247
|
+
# @example Use hash-like syntax to store the value
|
248
|
+
# c[:foo] = {bar: :baz}
|
249
|
+
#
|
250
|
+
# @example Set application specific flags
|
251
|
+
# c.set("foo", "bar", flags: 0x1000)
|
252
|
+
#
|
253
|
+
# @example Perform optimistic locking by specifying last known CAS version
|
254
|
+
# c.set("foo", "bar", cas: 8835713818674332672)
|
255
|
+
#
|
256
|
+
# @example Ensure that the key will be persisted at least on the one node
|
257
|
+
# c.set("foo", "bar", persist_to: 1)
|
258
|
+
def set(key, value, async: false, **opts)
|
259
|
+
# default operation is set
|
260
|
+
result @connection.store(key, value, **opts), async
|
261
|
+
end
|
262
|
+
alias_method :[]=, :set
|
263
|
+
|
264
|
+
# Replace the existing object in the database
|
265
|
+
#
|
266
|
+
# @param key [String, Symbol] Key used to reference the value.
|
267
|
+
# @param value [Object] Value to be stored
|
268
|
+
# @param options [Hash] Options for operation.
|
269
|
+
# @option options [Integer] :ttl Expiry time for key in seconds
|
270
|
+
# @option options [Integer] :expire_in Expiry time for key in seconds
|
271
|
+
# @option options [Integer, Time] :expire_at Unix epoc or time at which a key
|
272
|
+
# should expire
|
273
|
+
# @option options [Integer] :cas The CAS value for an object. This value is
|
274
|
+
# created on the server and is guaranteed to be unique for each value of
|
275
|
+
# a given key. This value is used to provide simple optimistic
|
276
|
+
# concurrency control when multiple clients or threads try to update an
|
277
|
+
# item simultaneously.
|
278
|
+
# @option options [Integer] :persist_to persist to a number of nodes before returing
|
279
|
+
# a result. Use -1 to persist to the maximum number of nodes
|
280
|
+
# @option options [Integer] :replicate_to replicate to a number of nodes before
|
281
|
+
# returning a result. Use -1 to replicate to the maximum number of nodes
|
282
|
+
#
|
283
|
+
# @return [Libcouchbase::Result] this includes the CAS value of the object.
|
284
|
+
#
|
285
|
+
# @raise [Libcouchbase::Error::KeyExists] if the key already exists on the server
|
286
|
+
# with a different CAS value to that provided
|
287
|
+
# @raise [Libouchbase::Error::Timedout] if timeout interval for observe exceeds
|
288
|
+
# @raise [Libouchbase::Error::NetworkError] if there was a communication issue
|
289
|
+
# @raise [Libcouchbase::Error::KeyNotFound] if the key doesn't exists
|
290
|
+
#
|
291
|
+
# @example Store the key which will be expired in 2 seconds using relative TTL.
|
292
|
+
# c.replace("foo", "bar", expire_in: 2)
|
293
|
+
#
|
294
|
+
# @example Store the key which will be expired in 2 seconds using absolute TTL.
|
295
|
+
# c.replace(:foo, :bar, expire_at: Time.now.to_i + 2)
|
296
|
+
#
|
297
|
+
# @example Set application specific flags
|
298
|
+
# c.replace("foo", "bar", flags: 0x1000)
|
299
|
+
#
|
300
|
+
# @example Ensure that the key will be persisted at least on the one node
|
301
|
+
# c.replace("foo", "bar", persist_to: 1)
|
302
|
+
def replace(key, value, async: false, **opts)
|
303
|
+
result @connection.store(key, value, **ReplaceDefaults.merge(opts)), async
|
304
|
+
end
|
305
|
+
ReplaceDefaults = {operation: :replace}.freeze
|
306
|
+
|
307
|
+
# Increment the value of an existing numeric key
|
308
|
+
#
|
309
|
+
# The increment method allow you to increase or decrease a given stored
|
310
|
+
# integer value. Updating the value of a key if it can be parsed to an integer.
|
311
|
+
# The update operation occurs on the server and is provided at the protocol
|
312
|
+
# level. This simplifies what would otherwise be a two-stage get and set
|
313
|
+
# operation.
|
314
|
+
#
|
315
|
+
# @param key [String, Symbol] Key used to reference the value.
|
316
|
+
# @param by [Integer] Integer (up to 64 bits) value to increment or decrement
|
317
|
+
# @param options [Hash] Options for operation.
|
318
|
+
# @option options [true, false] :create (false) If set to +true+, it will
|
319
|
+
# initialize the key with zero value and zero flags (use +:initial+
|
320
|
+
# option to set another initial value). Note: it won't increment the
|
321
|
+
# missing value.
|
322
|
+
# @option options [Integer] :initial (0) Integer (up to 64 bits) value for
|
323
|
+
# missing key initialization. This option imply +:create+ option is +true+
|
324
|
+
# @option options [Integer] :ttl Expiry time for key in seconds
|
325
|
+
# @option options [Integer] :expire_in Expiry time for key in seconds
|
326
|
+
# @option options [Integer, Time] :expire_at Unix epoc or time at which a key
|
327
|
+
# should expire
|
328
|
+
# @option options [true, false] :extended (false) If set to +true+, the
|
329
|
+
# operation will return a +Libcouchbase::Result+, otherwise (by default)
|
330
|
+
# it returns just the value.
|
331
|
+
#
|
332
|
+
# @return [Integer] the actual value of the key.
|
333
|
+
#
|
334
|
+
# @raise [Libouchbase::Error::Timedout] if timeout interval for observe exceeds
|
335
|
+
# @raise [Libouchbase::Error::NetworkError] if there was a communication issue
|
336
|
+
# @raise [Libcouchbase::Error::KeyNotFound] if the key doesn't exists
|
337
|
+
# @raise [Libcouchbase::Error::DeltaBadval] if the key contains non-numeric value
|
338
|
+
#
|
339
|
+
# @example Increment key by one
|
340
|
+
# c.incr(:foo)
|
341
|
+
#
|
342
|
+
# @example Increment key by 50
|
343
|
+
# c.incr("foo", 50)
|
344
|
+
#
|
345
|
+
# @example Increment key by one <b>OR</b> initialize with zero
|
346
|
+
# c.incr("foo", create: true) #=> will return old+1 or 0
|
347
|
+
#
|
348
|
+
# @example Increment key by one <b>OR</b> initialize with three
|
349
|
+
# c.incr("foo", 50, initial: 3) #=> will return old+50 or 3
|
350
|
+
#
|
351
|
+
# @example Increment key and get its CAS value
|
352
|
+
# resp = c.incr("foo", :extended => true)
|
353
|
+
# resp.cas #=> 12345
|
354
|
+
# resp.value #=> 2
|
355
|
+
def incr(key, by = 1, create: false, extended: false, async: false, **opts)
|
356
|
+
opts[:delta] ||= by
|
357
|
+
opts[:initial] = 0 if create
|
358
|
+
promise = @connection.counter(key, **opts)
|
359
|
+
if not extended
|
360
|
+
promise = promise.then { |resp| resp.value }
|
361
|
+
end
|
362
|
+
result promise, async
|
363
|
+
end
|
364
|
+
|
365
|
+
# Decrement the value of an existing numeric key
|
366
|
+
#
|
367
|
+
# Helper method, see incr
|
368
|
+
def decr(key, by = 1, **opts)
|
369
|
+
incr(key, -by, **opts)
|
370
|
+
end
|
371
|
+
|
372
|
+
# Delete the specified key
|
373
|
+
#
|
374
|
+
# @param key [String, Symbol] Key used to reference the value.
|
375
|
+
# @param options [Hash] Options for operation.
|
376
|
+
# @option options [Integer] :cas The CAS value for an object. This value is
|
377
|
+
# created on the server and is guaranteed to be unique for each value of
|
378
|
+
# a given key. This value is used to provide simple optimistic
|
379
|
+
# concurrency control when multiple clients or threads try to modify an
|
380
|
+
# item simultaneously.
|
381
|
+
# @option options [true, false] :quiet (self.quiet) If set to +true+, the
|
382
|
+
# operation won't raise error for missing key, it will return +nil+.
|
383
|
+
# Otherwise it will raise error.
|
384
|
+
#
|
385
|
+
# @return [true, false] the result of the operation.
|
386
|
+
#
|
387
|
+
# @raise [Libcouchbase::Error::KeyExists] if the key already exists on the server
|
388
|
+
# with a different CAS value to that provided
|
389
|
+
# @raise [Libouchbase::Error::Timedout] if timeout interval for observe exceeds
|
390
|
+
# @raise [Libouchbase::Error::NetworkError] if there was a communication issue
|
391
|
+
# @raise [Libcouchbase::Error::KeyNotFound] if the key doesn't exists
|
392
|
+
#
|
393
|
+
# @example Delete the key in quiet mode (default)
|
394
|
+
# c.set("foo", "bar")
|
395
|
+
# c.delete("foo") #=> true
|
396
|
+
# c.delete("foo") #=> false
|
397
|
+
#
|
398
|
+
# @example Delete the key verbosely
|
399
|
+
# c.set("foo", "bar")
|
400
|
+
# c.delete("foo", quiet: false) #=> true
|
401
|
+
# c.delete("foo", quiet: true) #=> nil (default behaviour)
|
402
|
+
# c.delete("foo", quiet: false) #=> will raise Libcouchbase::Error::KeyNotFound
|
403
|
+
#
|
404
|
+
# @example Delete the key with version check
|
405
|
+
# res = c.set("foo", "bar") #=> #<struct Libcouchbase::Response callback=:callback_set, key="foo", cas=1975457268957184, value="bar", metadata={:flags=>0}>
|
406
|
+
# c.delete("foo", cas: 123456) #=> will raise Libcouchbase::Error::KeyExists
|
407
|
+
# c.delete("foo", cas: res.cas) #=> true
|
408
|
+
def delete(key, async: false, quiet: true, **opts)
|
409
|
+
promise = @connection.remove(key, **opts).then { true }
|
410
|
+
if quiet
|
411
|
+
promise = promise.catch { |error|
|
412
|
+
if error.is_a? Libcouchbase::Error::KeyNotFound
|
413
|
+
false
|
414
|
+
else
|
415
|
+
::Libuv::Q.reject(@reactor, error)
|
416
|
+
end
|
417
|
+
}
|
418
|
+
end
|
419
|
+
result promise, async
|
420
|
+
end
|
421
|
+
|
422
|
+
# Delete contents of the bucket
|
423
|
+
#
|
424
|
+
# @see http://docs.couchbase.com/admin/admin/REST/rest-bucket-flush.html
|
425
|
+
#
|
426
|
+
# @raise [Libcouchbase::Error::HttpError] in case of an error is
|
427
|
+
# encountered.
|
428
|
+
#
|
429
|
+
# @return [Libcouchbase::Response]
|
430
|
+
#
|
431
|
+
# @example Simple flush the bucket
|
432
|
+
# c.flush
|
433
|
+
def flush(async: false)
|
434
|
+
result @connection.flush, async
|
435
|
+
end
|
436
|
+
|
437
|
+
# Touch a key, changing its CAS and optionally setting a timeout
|
438
|
+
def touch(key, async: false, **opts)
|
439
|
+
result @connection.touch(key, **opts), async
|
440
|
+
end
|
441
|
+
|
442
|
+
# Perform subdocument operations on a key.
|
443
|
+
#
|
444
|
+
# Yields a request builder to a block and applies the operations performed
|
445
|
+
#
|
446
|
+
# @param [String, Symbol] key
|
447
|
+
#
|
448
|
+
# @yieldparam [Libcouchbase::SubdocRequest] the subdocument request object used to define the request
|
449
|
+
#
|
450
|
+
# @example Perform a subdocument operation using a block
|
451
|
+
# c.subdoc(:foo) { |subdoc|
|
452
|
+
# subdoc.get('sub.key')
|
453
|
+
# subdoc.exists?('other.key')
|
454
|
+
# subdoc.get_count('some.array')
|
455
|
+
# } # => ["sub key val", true, 23]
|
456
|
+
#
|
457
|
+
# @example perform a subdocument operation using execute!
|
458
|
+
# c.subdoc(:foo).get(:bob).execute! # => { age: 13, working: false }
|
459
|
+
#
|
460
|
+
# @example perform multiple subdocument operations using execute!
|
461
|
+
# c.subdoc(:foo)
|
462
|
+
# .get(:bob).get(:jane).execute! # => [{ age: 13, working: false }, { age: 47, working: true }]
|
463
|
+
#
|
464
|
+
# @example perform a subdocument mutation operation
|
465
|
+
# c.subdoc(:foo).counter('bob.age', 1).execute! # => 14
|
466
|
+
def subdoc(key, quiet: @quiet, **opts)
|
467
|
+
if block_given?
|
468
|
+
sd = SubdocRequest.new(key, quiet)
|
469
|
+
yield sd
|
470
|
+
subdoc_execute!(sd, opts)
|
471
|
+
else
|
472
|
+
SubdocRequest.new(key, quiet, bucket: self, exec_opts: opts)
|
473
|
+
end
|
474
|
+
end
|
475
|
+
|
476
|
+
def subdoc_execute!(sd, extended: false, async: false, **opts)
|
477
|
+
promise = @connection.subdoc(sd, opts).then { |resp|
|
478
|
+
raise resp.value if resp.value.is_a?(::Exception)
|
479
|
+
extended ? resp : resp.value
|
480
|
+
}
|
481
|
+
result promise, async
|
482
|
+
end
|
483
|
+
|
484
|
+
# Fetch design docs stored in current bucket
|
485
|
+
#
|
486
|
+
# @return [Libcouchbase::DesignDocs]
|
487
|
+
def design_docs(**opts)
|
488
|
+
DesignDocs.new(self, @connection, proc { |promise, async| result(promise, async) }, **opts)
|
489
|
+
end
|
490
|
+
|
491
|
+
# Returns an enumerable for the results in a view.
|
492
|
+
#
|
493
|
+
# Results are lazily loaded when an operation is performed on the enum
|
494
|
+
#
|
495
|
+
# @return [Libcouchbase::Results]
|
496
|
+
def view(design, view, include_docs: true, is_spatial: false, **opts, &row_modifier)
|
497
|
+
view = @connection.query_view(design, view, **ViewDefaults.merge(opts))
|
498
|
+
view.include_docs = include_docs
|
499
|
+
view.is_spatial = is_spatial
|
500
|
+
|
501
|
+
current = ::Libuv::Reactor.current
|
502
|
+
|
503
|
+
if current && current.running?
|
504
|
+
ResultsLibuv.new(view, current, &row_modifier)
|
505
|
+
elsif Object.const_defined?(:EventMachine) && EM.reactor_thread?
|
506
|
+
ResultsEM.new(view, &row_modifier)
|
507
|
+
else
|
508
|
+
ResultsNative.new(view, &row_modifier)
|
509
|
+
end
|
510
|
+
end
|
511
|
+
ViewDefaults = {
|
512
|
+
on_error: :stop,
|
513
|
+
stale: false
|
514
|
+
}
|
515
|
+
|
516
|
+
# Returns an enumerable for the results in a full text search.
|
517
|
+
#
|
518
|
+
# Results are lazily loaded when an operation is performed on the enum
|
519
|
+
#
|
520
|
+
# @return [Libcouchbase::Results]
|
521
|
+
def full_text_search(index, query, **opts, &row_modifier)
|
522
|
+
if query.is_a? Hash
|
523
|
+
opts[:query] = query
|
524
|
+
else
|
525
|
+
opts[:query] = {query: query}
|
526
|
+
end
|
527
|
+
fts = @connection.full_text_search(index, **FtsDefaults.merge(opts))
|
528
|
+
|
529
|
+
current = ::Libuv::Reactor.current
|
530
|
+
if current && current.running?
|
531
|
+
ResultsLibuv.new(fts, current, &row_modifier)
|
532
|
+
elsif Object.const_defined?(:EventMachine) && EM.reactor_thread?
|
533
|
+
ResultsEM.new(fts, &row_modifier)
|
534
|
+
else
|
535
|
+
ResultsNative.new(fts, &row_modifier)
|
536
|
+
end
|
537
|
+
end
|
538
|
+
FtsDefaults = {
|
539
|
+
include_docs: true,
|
540
|
+
size: 10000, # Max result size
|
541
|
+
from: 0,
|
542
|
+
explain: false
|
543
|
+
}
|
544
|
+
|
545
|
+
# Returns an n1ql query builder.
|
546
|
+
#
|
547
|
+
# @return [Libcouchbase::N1QL]
|
548
|
+
def n1ql(**options)
|
549
|
+
N1QL.new(self, **options)
|
550
|
+
end
|
551
|
+
|
552
|
+
# Update or create design doc with supplied views
|
553
|
+
#
|
554
|
+
# @see http://docs.couchbase.com/admin/admin/REST/rest-ddocs-create.html
|
555
|
+
#
|
556
|
+
# @param [Hash, IO, String] data The source object containing JSON
|
557
|
+
# encoded design document.
|
558
|
+
def save_design_doc(data, id = nil, async: false)
|
559
|
+
attrs = case data
|
560
|
+
when String
|
561
|
+
JSON.parse(data, Connection::DECODE_OPTIONS)
|
562
|
+
when IO
|
563
|
+
JSON.parse(data.read, Connection::DECODE_OPTIONS)
|
564
|
+
when Hash
|
565
|
+
data
|
566
|
+
else
|
567
|
+
raise ArgumentError, "Document should be Hash, String or IO instance"
|
568
|
+
end
|
569
|
+
attrs[:language] ||= :javascript
|
570
|
+
|
571
|
+
id ||= attrs.delete(:_id)
|
572
|
+
id = id.to_s.sub(/^_design\//, '')
|
573
|
+
|
574
|
+
prom = @connection.http("/_design/#{id}",
|
575
|
+
method: :put,
|
576
|
+
body: attrs,
|
577
|
+
type: :view
|
578
|
+
).then { |res|
|
579
|
+
# Seems to require a moment before the view is usable
|
580
|
+
@reactor.sleep 100
|
581
|
+
res
|
582
|
+
}
|
583
|
+
|
584
|
+
result prom, async
|
585
|
+
end
|
586
|
+
|
587
|
+
# Delete design doc with given id and optional revision.
|
588
|
+
#
|
589
|
+
# @see http://docs.couchbase.com/admin/admin/REST/rest-ddocs-delete.html
|
590
|
+
#
|
591
|
+
# @param [String, Symbol] id ID of the design doc
|
592
|
+
# @param [String] rev Optional revision
|
593
|
+
def delete_design_doc(id, rev = nil, async: false)
|
594
|
+
id = id.to_s.sub(/^_design\//, '')
|
595
|
+
rev = "?rev=#{rev}" if rev
|
596
|
+
result @connection.http("/_design/#{id}#{rev}", method: :delete, type: :view), async
|
597
|
+
end
|
598
|
+
|
599
|
+
# Compare and swap value.
|
600
|
+
#
|
601
|
+
# Reads a key's value from the server and yields it to a block. Replaces
|
602
|
+
# the key's value with the result of the block as long as the key hasn't
|
603
|
+
# been updated in the meantime, otherwise raises
|
604
|
+
# {Libcouchbase::Error::KeyExists}.
|
605
|
+
#
|
606
|
+
# Setting the +:retry+ option to a positive number will cause this method
|
607
|
+
# to rescue the {Libcouchbase::Error::KeyExists} error that happens when
|
608
|
+
# an update collision is detected, and automatically get a fresh copy
|
609
|
+
# of the value and retry the block. This will repeat as long as there
|
610
|
+
# continues to be conflicts, up to the maximum number of retries specified.
|
611
|
+
#
|
612
|
+
# @param [String, Symbol] key
|
613
|
+
#
|
614
|
+
# @param [Hash] options the options for "swap" part
|
615
|
+
# @option options [Integer] :retry (0) maximum number of times to autmatically retry upon update collision
|
616
|
+
#
|
617
|
+
# @yieldparam [Object] value existing value
|
618
|
+
# @yieldreturn [Object] new value.
|
619
|
+
#
|
620
|
+
# @raise [Couchbase::Error::KeyExists] if the key was updated before the the
|
621
|
+
# code in block has been completed (the CAS value has been changed).
|
622
|
+
# @raise [ArgumentError] if the block is missing
|
623
|
+
#
|
624
|
+
# @example Implement append to JSON encoded value
|
625
|
+
#
|
626
|
+
# c.set(:foo, {bar: 1})
|
627
|
+
# c.cas(:foo) do |val|
|
628
|
+
# val[:baz] = 2
|
629
|
+
# val
|
630
|
+
# end
|
631
|
+
# c.get(:foo) #=> {bar: 1, baz: 2}
|
632
|
+
#
|
633
|
+
# @return [Libcouchbase::Response] the transaction details including the new CAS
|
634
|
+
def compare_and_swap(key, **opts)
|
635
|
+
retries = opts.delete(:retry) || 0
|
636
|
+
begin
|
637
|
+
current = result(@connection.get(key))
|
638
|
+
new_value = yield current.value, opts
|
639
|
+
opts[:cas] = current.cas
|
640
|
+
|
641
|
+
set(key, new_value, **opts)
|
642
|
+
rescue Libcouchbase::Error::KeyExists
|
643
|
+
retries -= 1
|
644
|
+
retry if retries >= 0
|
645
|
+
raise
|
646
|
+
end
|
647
|
+
end
|
648
|
+
alias_method :cas, :compare_and_swap
|
649
|
+
|
650
|
+
# The numbers of the replicas for each node in the cluster
|
651
|
+
# @return [Integer]
|
652
|
+
def get_num_replicas
|
653
|
+
result @connection.get_num_replicas
|
654
|
+
end
|
655
|
+
|
656
|
+
# The numbers of nodes in the cluster
|
657
|
+
# @return [Integer]
|
658
|
+
def get_num_nodes
|
659
|
+
result @connection.get_num_nodes
|
660
|
+
end
|
661
|
+
|
662
|
+
# Waits for all the async operations to complete and returns the results
|
663
|
+
#
|
664
|
+
# @return [Array]
|
665
|
+
def wait_results(*results)
|
666
|
+
result ::Libuv::Q.all(@reactor, *results.flatten)
|
667
|
+
end
|
668
|
+
|
669
|
+
|
670
|
+
protected
|
671
|
+
|
672
|
+
|
673
|
+
def result(promise, async = false)
|
674
|
+
return promise if async
|
675
|
+
|
676
|
+
current = ::Libuv::Reactor.current
|
677
|
+
if current && current.running?
|
678
|
+
promise.value
|
679
|
+
elsif Object.const_defined?(:EventMachine) && EM.reactor_thread?
|
680
|
+
# Assume this is being run in em-synchrony
|
681
|
+
f = Fiber.current
|
682
|
+
error = nil
|
683
|
+
response = nil
|
684
|
+
|
685
|
+
@connection.reactor.next_tick do
|
686
|
+
begin
|
687
|
+
response = promise.value
|
688
|
+
rescue Exception => e
|
689
|
+
error = e
|
690
|
+
end
|
691
|
+
|
692
|
+
EM.next_tick {
|
693
|
+
f.resume
|
694
|
+
}
|
695
|
+
end
|
696
|
+
|
697
|
+
Fiber.yield
|
698
|
+
|
699
|
+
update_backtrace(error) if error
|
700
|
+
response
|
701
|
+
else
|
702
|
+
request = Mutex.new
|
703
|
+
result = ConditionVariable.new
|
704
|
+
error = nil
|
705
|
+
response = nil
|
706
|
+
|
707
|
+
request.synchronize {
|
708
|
+
@connection.reactor.next_tick do
|
709
|
+
begin
|
710
|
+
response = promise.value
|
711
|
+
rescue Exception => e
|
712
|
+
error = e
|
713
|
+
end
|
714
|
+
|
715
|
+
# Odds are we won't actually block here
|
716
|
+
request.synchronize {
|
717
|
+
result.signal
|
718
|
+
}
|
719
|
+
end
|
720
|
+
result.wait(request)
|
721
|
+
}
|
722
|
+
|
723
|
+
update_backtrace(error) if error
|
724
|
+
response
|
725
|
+
end
|
726
|
+
end
|
727
|
+
|
728
|
+
def connect
|
729
|
+
if @connection.reactor.running?
|
730
|
+
# We don't need to start a reactor so we use the regular helper
|
731
|
+
result(@connection.connect)
|
732
|
+
elsif Object.const_defined?(:EventMachine) && EM.reactor_thread?
|
733
|
+
start_reactor_and_em_connect
|
734
|
+
else
|
735
|
+
start_reactor_and_connect
|
736
|
+
end
|
737
|
+
end
|
738
|
+
|
739
|
+
# This blocks on the current thread
|
740
|
+
def start_reactor_and_connect
|
741
|
+
connecting = Mutex.new
|
742
|
+
result = ConditionVariable.new
|
743
|
+
error = nil
|
744
|
+
|
745
|
+
connecting.synchronize {
|
746
|
+
Thread.new do
|
747
|
+
@connection.reactor.run do |reactor|
|
748
|
+
reactor.ref
|
749
|
+
|
750
|
+
attempt = 0
|
751
|
+
begin
|
752
|
+
@connection.connect.value
|
753
|
+
rescue Libcouchbase::Error::ConnectError => e
|
754
|
+
attempt += 1
|
755
|
+
if attempt < 3
|
756
|
+
reactor.sleep 200
|
757
|
+
# Requires a new connection object or the retry will always fail
|
758
|
+
@connection = Connection.new(**@connection_options)
|
759
|
+
retry
|
760
|
+
end
|
761
|
+
error = e
|
762
|
+
rescue Exception => e
|
763
|
+
error = e
|
764
|
+
end
|
765
|
+
|
766
|
+
# Odds are we won't actually block here
|
767
|
+
connecting.synchronize {
|
768
|
+
result.signal
|
769
|
+
}
|
770
|
+
end
|
771
|
+
end
|
772
|
+
result.wait(connecting)
|
773
|
+
}
|
774
|
+
|
775
|
+
update_backtrace(error) if error
|
776
|
+
end
|
777
|
+
|
778
|
+
# Assume this is being run in em-synchrony
|
779
|
+
def start_reactor_and_em_connect
|
780
|
+
f = Fiber.current
|
781
|
+
error = nil
|
782
|
+
|
783
|
+
Thread.new do
|
784
|
+
@connection.reactor.run do |reactor|
|
785
|
+
reactor.ref
|
786
|
+
|
787
|
+
attempt = 0
|
788
|
+
begin
|
789
|
+
@connection.connect.value
|
790
|
+
rescue Libcouchbase::Error::ConnectError => e
|
791
|
+
attempt += 1
|
792
|
+
if attempt < 3
|
793
|
+
reactor.sleep 200
|
794
|
+
# Requires a new connection object or the retry will always fail
|
795
|
+
@connection = Connection.new(**@connection_options)
|
796
|
+
retry
|
797
|
+
end
|
798
|
+
error = e
|
799
|
+
rescue Exception => e
|
800
|
+
error = e
|
801
|
+
end
|
802
|
+
|
803
|
+
EM.next_tick {
|
804
|
+
f.resume
|
805
|
+
}
|
806
|
+
end
|
807
|
+
end
|
808
|
+
|
809
|
+
Fiber.yield
|
810
|
+
|
811
|
+
update_backtrace(error) if error
|
812
|
+
end
|
813
|
+
|
814
|
+
def update_backtrace(error)
|
815
|
+
backtrace = caller
|
816
|
+
backtrace.shift(2)
|
817
|
+
if error.respond_to?(:backtrace) && error.backtrace
|
818
|
+
backtrace << '---- continuation ----'
|
819
|
+
backtrace.concat(error.backtrace)
|
820
|
+
end
|
821
|
+
error.set_backtrace(backtrace)
|
822
|
+
raise error
|
823
|
+
end
|
824
|
+
end
|
825
|
+
end
|