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.
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