libcouchbase-mapo 1.4.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (104) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +20 -0
  3. data/.gitmodules +3 -0
  4. data/.rspec +1 -0
  5. data/.travis.yml +38 -0
  6. data/Gemfile +4 -0
  7. data/LICENSE +24 -0
  8. data/README.md +445 -0
  9. data/Rakefile +76 -0
  10. data/ext/README.md +6 -0
  11. data/ext/Rakefile +19 -0
  12. data/lib/libcouchbase.rb +40 -0
  13. data/lib/libcouchbase/bucket.rb +825 -0
  14. data/lib/libcouchbase/callbacks.rb +69 -0
  15. data/lib/libcouchbase/connection.rb +886 -0
  16. data/lib/libcouchbase/design_docs.rb +92 -0
  17. data/lib/libcouchbase/error.rb +68 -0
  18. data/lib/libcouchbase/ext/libcouchbase.rb +1175 -0
  19. data/lib/libcouchbase/ext/libcouchbase/cmdbase.rb +23 -0
  20. data/lib/libcouchbase/ext/libcouchbase/cmdcounter.rb +36 -0
  21. data/lib/libcouchbase/ext/libcouchbase/cmdendure.rb +26 -0
  22. data/lib/libcouchbase/ext/libcouchbase/cmdfts.rb +24 -0
  23. data/lib/libcouchbase/ext/libcouchbase/cmdget.rb +30 -0
  24. data/lib/libcouchbase/ext/libcouchbase/cmdgetreplica.rb +49 -0
  25. data/lib/libcouchbase/ext/libcouchbase/cmdhttp.rb +58 -0
  26. data/lib/libcouchbase/ext/libcouchbase/cmdn1ql.rb +40 -0
  27. data/lib/libcouchbase/ext/libcouchbase/cmdobseqno.rb +33 -0
  28. data/lib/libcouchbase/ext/libcouchbase/cmdobserve.rb +30 -0
  29. data/lib/libcouchbase/ext/libcouchbase/cmdstore.rb +40 -0
  30. data/lib/libcouchbase/ext/libcouchbase/cmdstoredur.rb +45 -0
  31. data/lib/libcouchbase/ext/libcouchbase/cmdsubdoc.rb +61 -0
  32. data/lib/libcouchbase/ext/libcouchbase/cmdverbosity.rb +29 -0
  33. data/lib/libcouchbase/ext/libcouchbase/cmdviewquery.rb +61 -0
  34. data/lib/libcouchbase/ext/libcouchbase/contigbuf.rb +14 -0
  35. data/lib/libcouchbase/ext/libcouchbase/create_st.rb +15 -0
  36. data/lib/libcouchbase/ext/libcouchbase/create_st0.rb +23 -0
  37. data/lib/libcouchbase/ext/libcouchbase/create_st1.rb +26 -0
  38. data/lib/libcouchbase/ext/libcouchbase/create_st2.rb +32 -0
  39. data/lib/libcouchbase/ext/libcouchbase/create_st3.rb +26 -0
  40. data/lib/libcouchbase/ext/libcouchbase/crst_u.rb +20 -0
  41. data/lib/libcouchbase/ext/libcouchbase/durability_opts_st_v.rb +11 -0
  42. data/lib/libcouchbase/ext/libcouchbase/durability_opts_t.rb +14 -0
  43. data/lib/libcouchbase/ext/libcouchbase/durabilityopt_sv0.rb +63 -0
  44. data/lib/libcouchbase/ext/libcouchbase/enums.rb +1007 -0
  45. data/lib/libcouchbase/ext/libcouchbase/fragbuf.rb +18 -0
  46. data/lib/libcouchbase/ext/libcouchbase/ftshandle.rb +7 -0
  47. data/lib/libcouchbase/ext/libcouchbase/histogram.rb +34 -0
  48. data/lib/libcouchbase/ext/libcouchbase/http_request_t.rb +7 -0
  49. data/lib/libcouchbase/ext/libcouchbase/keybuf.rb +20 -0
  50. data/lib/libcouchbase/ext/libcouchbase/multicmd_ctx.rb +30 -0
  51. data/lib/libcouchbase/ext/libcouchbase/mutation_token.rb +17 -0
  52. data/lib/libcouchbase/ext/libcouchbase/n1qlhandle.rb +7 -0
  53. data/lib/libcouchbase/ext/libcouchbase/n1qlparams.rb +7 -0
  54. data/lib/libcouchbase/ext/libcouchbase/respbase.rb +29 -0
  55. data/lib/libcouchbase/ext/libcouchbase/respcounter.rb +32 -0
  56. data/lib/libcouchbase/ext/libcouchbase/respendure.rb +49 -0
  57. data/lib/libcouchbase/ext/libcouchbase/respfts.rb +40 -0
  58. data/lib/libcouchbase/ext/libcouchbase/respget.rb +44 -0
  59. data/lib/libcouchbase/ext/libcouchbase/resphttp.rb +48 -0
  60. data/lib/libcouchbase/ext/libcouchbase/respmcversion.rb +38 -0
  61. data/lib/libcouchbase/ext/libcouchbase/respn1ql.rb +41 -0
  62. data/lib/libcouchbase/ext/libcouchbase/respobseqno.rb +52 -0
  63. data/lib/libcouchbase/ext/libcouchbase/respobserve.rb +41 -0
  64. data/lib/libcouchbase/ext/libcouchbase/respserverbase.rb +32 -0
  65. data/lib/libcouchbase/ext/libcouchbase/respstats.rb +38 -0
  66. data/lib/libcouchbase/ext/libcouchbase/respstore.rb +32 -0
  67. data/lib/libcouchbase/ext/libcouchbase/respstoredur.rb +38 -0
  68. data/lib/libcouchbase/ext/libcouchbase/respsubdoc.rb +35 -0
  69. data/lib/libcouchbase/ext/libcouchbase/respviewquery.rb +67 -0
  70. data/lib/libcouchbase/ext/libcouchbase/sdentry.rb +22 -0
  71. data/lib/libcouchbase/ext/libcouchbase/sdspec.rb +31 -0
  72. data/lib/libcouchbase/ext/libcouchbase/t.rb +7 -0
  73. data/lib/libcouchbase/ext/libcouchbase/valbuf.rb +22 -0
  74. data/lib/libcouchbase/ext/libcouchbase/valbuf_u_buf.rb +14 -0
  75. data/lib/libcouchbase/ext/libcouchbase/viewhandle.rb +7 -0
  76. data/lib/libcouchbase/ext/libcouchbase_libuv.rb +22 -0
  77. data/lib/libcouchbase/ext/tasks.rb +39 -0
  78. data/lib/libcouchbase/n1ql.rb +78 -0
  79. data/lib/libcouchbase/query_full_text.rb +147 -0
  80. data/lib/libcouchbase/query_n1ql.rb +123 -0
  81. data/lib/libcouchbase/query_view.rb +135 -0
  82. data/lib/libcouchbase/results_fiber.rb +281 -0
  83. data/lib/libcouchbase/results_native.rb +220 -0
  84. data/lib/libcouchbase/subdoc_request.rb +139 -0
  85. data/lib/libcouchbase/version.rb +5 -0
  86. data/libcouchbase.gemspec +68 -0
  87. data/spec/bucket_spec.rb +290 -0
  88. data/spec/connection_spec.rb +257 -0
  89. data/spec/design_docs_spec.rb +31 -0
  90. data/spec/error_spec.rb +26 -0
  91. data/spec/fts_spec.rb +135 -0
  92. data/spec/n1ql_spec.rb +206 -0
  93. data/spec/results_libuv_spec.rb +244 -0
  94. data/spec/results_native_spec.rb +259 -0
  95. data/spec/seed/2016-10-25T043505Z/2016-10-25T043505Z-full/bucket-default/design.json +1 -0
  96. data/spec/seed/2016-10-25T043505Z/2016-10-25T043505Z-full/bucket-default/node-127.0.0.1%3A8091/data-0000.cbb +0 -0
  97. data/spec/seed/2016-10-25T043505Z/2016-10-25T043505Z-full/bucket-default/node-127.0.0.1%3A8091/failover.json +1 -0
  98. data/spec/seed/2016-10-25T043505Z/2016-10-25T043505Z-full/bucket-default/node-127.0.0.1%3A8091/meta.json +1 -0
  99. data/spec/seed/2016-10-25T043505Z/2016-10-25T043505Z-full/bucket-default/node-127.0.0.1%3A8091/seqno.json +1 -0
  100. data/spec/seed/2016-10-25T043505Z/2016-10-25T043505Z-full/bucket-default/node-127.0.0.1%3A8091/snapshot_markers.json +1 -0
  101. data/spec/subdoc_spec.rb +192 -0
  102. data/spec/view_spec.rb +201 -0
  103. data/windows_build.md +36 -0
  104. metadata +265 -0
@@ -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
@@ -0,0 +1,6 @@
1
+ To compile a libcouchbase.(so|dll|dylib) go to root directory of libuv project and run:
2
+
3
+ ```shell
4
+ git submodule update --init
5
+ rake compile
6
+ ```
@@ -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
@@ -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