couchbase 3.5.0-x86_64-linux-musl

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 (125) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE.txt +202 -0
  3. data/README.md +154 -0
  4. data/ext/extconf.rb +0 -0
  5. data/lib/active_support/cache/couchbase_store.rb +339 -0
  6. data/lib/couchbase/3.1/libcouchbase.so +0 -0
  7. data/lib/couchbase/3.2/libcouchbase.so +0 -0
  8. data/lib/couchbase/3.3/libcouchbase.so +0 -0
  9. data/lib/couchbase/analytics_options.rb +107 -0
  10. data/lib/couchbase/authenticator.rb +64 -0
  11. data/lib/couchbase/binary_collection.rb +128 -0
  12. data/lib/couchbase/binary_collection_options.rb +24 -0
  13. data/lib/couchbase/bucket.rb +144 -0
  14. data/lib/couchbase/cluster.rb +460 -0
  15. data/lib/couchbase/cluster_registry.rb +49 -0
  16. data/lib/couchbase/collection.rb +705 -0
  17. data/lib/couchbase/collection_options.rb +399 -0
  18. data/lib/couchbase/config_profiles.rb +55 -0
  19. data/lib/couchbase/configuration.rb +56 -0
  20. data/lib/couchbase/datastructures/couchbase_list.rb +160 -0
  21. data/lib/couchbase/datastructures/couchbase_map.rb +194 -0
  22. data/lib/couchbase/datastructures/couchbase_queue.rb +134 -0
  23. data/lib/couchbase/datastructures/couchbase_set.rb +128 -0
  24. data/lib/couchbase/datastructures.rb +24 -0
  25. data/lib/couchbase/diagnostics.rb +181 -0
  26. data/lib/couchbase/errors.rb +376 -0
  27. data/lib/couchbase/json_transcoder.rb +39 -0
  28. data/lib/couchbase/key_value_scan.rb +117 -0
  29. data/lib/couchbase/libcouchbase.rb +6 -0
  30. data/lib/couchbase/logger.rb +85 -0
  31. data/lib/couchbase/management/analytics_index_manager.rb +1127 -0
  32. data/lib/couchbase/management/bucket_manager.rb +443 -0
  33. data/lib/couchbase/management/collection_manager.rb +470 -0
  34. data/lib/couchbase/management/collection_query_index_manager.rb +222 -0
  35. data/lib/couchbase/management/query_index_manager.rb +617 -0
  36. data/lib/couchbase/management/scope_search_index_manager.rb +198 -0
  37. data/lib/couchbase/management/search_index_manager.rb +424 -0
  38. data/lib/couchbase/management/user_manager.rb +468 -0
  39. data/lib/couchbase/management/view_index_manager.rb +237 -0
  40. data/lib/couchbase/management.rb +29 -0
  41. data/lib/couchbase/mutation_state.rb +63 -0
  42. data/lib/couchbase/options.rb +2837 -0
  43. data/lib/couchbase/protostellar/binary_collection.rb +55 -0
  44. data/lib/couchbase/protostellar/bucket.rb +51 -0
  45. data/lib/couchbase/protostellar/client.rb +99 -0
  46. data/lib/couchbase/protostellar/cluster.rb +163 -0
  47. data/lib/couchbase/protostellar/collection.rb +152 -0
  48. data/lib/couchbase/protostellar/connect_options.rb +63 -0
  49. data/lib/couchbase/protostellar/error_handling.rb +203 -0
  50. data/lib/couchbase/protostellar/generated/admin/bucket/v1/bucket_pb.rb +61 -0
  51. data/lib/couchbase/protostellar/generated/admin/bucket/v1/bucket_services_pb.rb +35 -0
  52. data/lib/couchbase/protostellar/generated/admin/collection/v1/collection_pb.rb +57 -0
  53. data/lib/couchbase/protostellar/generated/admin/collection/v1/collection_services_pb.rb +36 -0
  54. data/lib/couchbase/protostellar/generated/admin/query/v1/query_pb.rb +61 -0
  55. data/lib/couchbase/protostellar/generated/admin/query/v1/query_services_pb.rb +37 -0
  56. data/lib/couchbase/protostellar/generated/admin/search/v1/search_pb.rb +72 -0
  57. data/lib/couchbase/protostellar/generated/admin/search/v1/search_services_pb.rb +44 -0
  58. data/lib/couchbase/protostellar/generated/analytics/v1/analytics_pb.rb +52 -0
  59. data/lib/couchbase/protostellar/generated/analytics/v1/analytics_services_pb.rb +30 -0
  60. data/lib/couchbase/protostellar/generated/internal/hooks/v1/hooks_pb.rb +70 -0
  61. data/lib/couchbase/protostellar/generated/internal/hooks/v1/hooks_services_pb.rb +36 -0
  62. data/lib/couchbase/protostellar/generated/kv/v1/kv_pb.rb +97 -0
  63. data/lib/couchbase/protostellar/generated/kv/v1/kv_services_pb.rb +46 -0
  64. data/lib/couchbase/protostellar/generated/query/v1/query_pb.rb +57 -0
  65. data/lib/couchbase/protostellar/generated/query/v1/query_services_pb.rb +30 -0
  66. data/lib/couchbase/protostellar/generated/routing/v1/routing_pb.rb +52 -0
  67. data/lib/couchbase/protostellar/generated/routing/v1/routing_services_pb.rb +30 -0
  68. data/lib/couchbase/protostellar/generated/search/v1/search_pb.rb +99 -0
  69. data/lib/couchbase/protostellar/generated/search/v1/search_services_pb.rb +30 -0
  70. data/lib/couchbase/protostellar/generated/transactions/v1/transactions_pb.rb +57 -0
  71. data/lib/couchbase/protostellar/generated/transactions/v1/transactions_services_pb.rb +36 -0
  72. data/lib/couchbase/protostellar/generated/view/v1/view_pb.rb +51 -0
  73. data/lib/couchbase/protostellar/generated/view/v1/view_services_pb.rb +30 -0
  74. data/lib/couchbase/protostellar/generated.rb +9 -0
  75. data/lib/couchbase/protostellar/management/bucket_manager.rb +67 -0
  76. data/lib/couchbase/protostellar/management/collection_manager.rb +94 -0
  77. data/lib/couchbase/protostellar/management/collection_query_index_manager.rb +124 -0
  78. data/lib/couchbase/protostellar/management/query_index_manager.rb +112 -0
  79. data/lib/couchbase/protostellar/management.rb +24 -0
  80. data/lib/couchbase/protostellar/request.rb +78 -0
  81. data/lib/couchbase/protostellar/request_behaviour.rb +42 -0
  82. data/lib/couchbase/protostellar/request_generator/admin/bucket.rb +124 -0
  83. data/lib/couchbase/protostellar/request_generator/admin/collection.rb +94 -0
  84. data/lib/couchbase/protostellar/request_generator/admin/query.rb +130 -0
  85. data/lib/couchbase/protostellar/request_generator/admin.rb +24 -0
  86. data/lib/couchbase/protostellar/request_generator/kv.rb +474 -0
  87. data/lib/couchbase/protostellar/request_generator/query.rb +133 -0
  88. data/lib/couchbase/protostellar/request_generator/search.rb +387 -0
  89. data/lib/couchbase/protostellar/request_generator.rb +26 -0
  90. data/lib/couchbase/protostellar/response_converter/admin/bucket.rb +55 -0
  91. data/lib/couchbase/protostellar/response_converter/admin/collection.rb +42 -0
  92. data/lib/couchbase/protostellar/response_converter/admin/query.rb +59 -0
  93. data/lib/couchbase/protostellar/response_converter/admin.rb +24 -0
  94. data/lib/couchbase/protostellar/response_converter/kv.rb +151 -0
  95. data/lib/couchbase/protostellar/response_converter/query.rb +84 -0
  96. data/lib/couchbase/protostellar/response_converter/search.rb +136 -0
  97. data/lib/couchbase/protostellar/response_converter.rb +26 -0
  98. data/lib/couchbase/protostellar/retry/action.rb +38 -0
  99. data/lib/couchbase/protostellar/retry/orchestrator.rb +60 -0
  100. data/lib/couchbase/protostellar/retry/reason.rb +67 -0
  101. data/lib/couchbase/protostellar/retry/strategies/best_effort.rb +49 -0
  102. data/lib/couchbase/protostellar/retry/strategies.rb +26 -0
  103. data/lib/couchbase/protostellar/retry.rb +28 -0
  104. data/lib/couchbase/protostellar/scope.rb +57 -0
  105. data/lib/couchbase/protostellar/timeout_defaults.rb +30 -0
  106. data/lib/couchbase/protostellar/timeouts.rb +83 -0
  107. data/lib/couchbase/protostellar.rb +29 -0
  108. data/lib/couchbase/query_options.rb +120 -0
  109. data/lib/couchbase/railtie.rb +45 -0
  110. data/lib/couchbase/raw_binary_transcoder.rb +37 -0
  111. data/lib/couchbase/raw_json_transcoder.rb +38 -0
  112. data/lib/couchbase/raw_string_transcoder.rb +40 -0
  113. data/lib/couchbase/scope.rb +256 -0
  114. data/lib/couchbase/search_options.rb +1622 -0
  115. data/lib/couchbase/subdoc.rb +290 -0
  116. data/lib/couchbase/transcoder_flags.rb +62 -0
  117. data/lib/couchbase/utils/generic_logger_adapter.rb +38 -0
  118. data/lib/couchbase/utils/stdlib_logger_adapter.rb +65 -0
  119. data/lib/couchbase/utils/time.rb +69 -0
  120. data/lib/couchbase/utils.rb +21 -0
  121. data/lib/couchbase/version.rb +23 -0
  122. data/lib/couchbase/view_options.rb +65 -0
  123. data/lib/couchbase.rb +28 -0
  124. data/lib/rails/generators/couchbase/config/config_generator.rb +27 -0
  125. metadata +190 -0
@@ -0,0 +1,705 @@
1
+ # Copyright 2020-2021 Couchbase, Inc.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ require "couchbase/errors"
16
+ require "couchbase/collection_options"
17
+ require "couchbase/binary_collection"
18
+ require "couchbase/key_value_scan"
19
+
20
+ module Couchbase
21
+ # Provides access to all collection APIs
22
+ class Collection
23
+ attr_reader :bucket_name
24
+ attr_reader :scope_name
25
+ attr_reader :name
26
+
27
+ alias inspect to_s
28
+
29
+ # @param [Couchbase::Backend] backend
30
+ # @param [String] bucket_name name of the bucket
31
+ # @param [String] scope_name name of the scope
32
+ # @param [String] collection_name name of the collection
33
+ def initialize(backend, bucket_name, scope_name, collection_name)
34
+ @backend = backend
35
+ @bucket_name = bucket_name
36
+ @scope_name = scope_name
37
+ @name = collection_name
38
+ end
39
+
40
+ # Provides access to the binary APIs, not used for JSON documents
41
+ #
42
+ # @return [BinaryCollection]
43
+ def binary
44
+ BinaryCollection.new(self)
45
+ end
46
+
47
+ # @return [Management::CollectionQueryIndexManager]
48
+ def query_indexes
49
+ Management::CollectionQueryIndexManager.new(@backend, @bucket_name, @scope_name, @name)
50
+ end
51
+
52
+ # Fetches the full document from the collection
53
+ #
54
+ # @param [String] id the document id which is used to uniquely identify it
55
+ # @param [Options::Get] options request customization
56
+ #
57
+ # @example Get document contents
58
+ # res = collection.get("customer123")
59
+ # res.content["addresses"]
60
+ #
61
+ # # {"billing"=>
62
+ # # {"line1"=>"123 Any Street", "line2"=>"Anytown", "country"=>"United Kingdom"},
63
+ # # "delivery"=>
64
+ # # {"line1"=>"123 Any Street", "line2"=>"Anytown", "country"=>"United Kingdom"}}
65
+ #
66
+ # @example Get partial document using projections
67
+ # res = collection.get("customer123", Options::Get(projections: ["name", "addresses.billing"]))
68
+ # res.content
69
+ #
70
+ # # {"addresses"=>
71
+ # # {"billing"=>
72
+ # # {"country"=>"United Kingdom",
73
+ # # "line1"=>"123 Any Street",
74
+ # # "line2"=>"Anytown"}},
75
+ # # "name"=>"Douglas Reynholm"}
76
+ #
77
+ # @return [GetResult]
78
+ def get(id, options = Options::Get::DEFAULT)
79
+ resp = if options.need_projected_get?
80
+ @backend.document_get_projected(bucket_name, @scope_name, @name, id, options.to_backend)
81
+ else
82
+ @backend.document_get(bucket_name, @scope_name, @name, id, options.to_backend)
83
+ end
84
+ GetResult.new do |res|
85
+ res.transcoder = options.transcoder
86
+ res.cas = resp[:cas]
87
+ res.flags = resp[:flags]
88
+ res.encoded = resp[:content]
89
+ res.expiry = resp[:expiry] if resp.key?(:expiry)
90
+ end
91
+ end
92
+
93
+ # Fetches multiple documents from the collection.
94
+ #
95
+ # @note that it will not generate {Error::DocumentNotFound} exceptions in this case. The caller should check
96
+ # {GetResult#error} property of the result
97
+ #
98
+ # @param [Array<String>] ids the array of document identifiers
99
+ # @param [Options::GetMulti] options request customization
100
+ #
101
+ # @example Fetch "foo" and "bar" in a batch
102
+ # res = collection.get(["foo", "bar"], Options::GetMulti(timeout: 3_000))
103
+ # res[0].content #=> content of "foo"
104
+ # res[1].content #=> content of "bar"
105
+ #
106
+ # @return [Array<GetResult>]
107
+ def get_multi(ids, options = Options::GetMulti::DEFAULT)
108
+ resp = @backend.document_get_multi(ids.map { |id| [bucket_name, @scope_name, @name, id] }, options.to_backend)
109
+ resp.map do |entry|
110
+ GetResult.new do |res|
111
+ res.transcoder = options.transcoder
112
+ res.id = entry[:id]
113
+ res.cas = entry[:cas]
114
+ res.flags = entry[:flags]
115
+ res.encoded = entry[:content]
116
+ res.error = entry[:error]
117
+ end
118
+ end
119
+ end
120
+
121
+ # Fetches the full document and write-locks it for the given duration
122
+ #
123
+ # @param [String] id the document id which is used to uniquely identify it.
124
+ # @param [Integer, #in_seconds] lock_time how long to lock the document (values over 30 seconds will be capped)
125
+ # @param [Options::GetAndLock] options request customization
126
+ #
127
+ # @example Retrieve document and lock for 10 seconds
128
+ # collection.get_and_lock("customer123", 10, Options::GetAndLock(timeout: 3_000))
129
+ #
130
+ # @example Update document pessimistically
131
+ # res = collection.get_and_lock("customer123", 10)
132
+ # user_data = res.content
133
+ # user_data["admin"] = true
134
+ # collection.replace("user", user_data, Options::Upsert(cas: res.cas))
135
+ #
136
+ # @return [GetResult]
137
+ def get_and_lock(id, lock_time, options = Options::GetAndLock::DEFAULT)
138
+ resp = @backend.document_get_and_lock(bucket_name, @scope_name, @name, id,
139
+ lock_time.respond_to?(:in_seconds) ? lock_time.public_send(:in_seconds) : lock_time,
140
+ options.to_backend)
141
+ GetResult.new do |res|
142
+ res.transcoder = options.transcoder
143
+ res.cas = resp[:cas]
144
+ res.flags = resp[:flags]
145
+ res.encoded = resp[:content]
146
+ end
147
+ end
148
+
149
+ # Fetches a full document and resets its expiration time to the duration provided
150
+ #
151
+ # @param [String] id the document id which is used to uniquely identify it.
152
+ # @param [Integer, #in_seconds, Time] expiry the new expiration time for the document
153
+ # @param [Options::GetAndTouch] options request customization
154
+ #
155
+ # @example Retrieve document and prolong its expiration for another 10 seconds
156
+ # collection.get_and_touch("customer123", 10)
157
+ #
158
+ # @return [GetResult]
159
+ def get_and_touch(id, expiry, options = Options::GetAndTouch::DEFAULT)
160
+ resp = @backend.document_get_and_touch(bucket_name, @scope_name, @name, id,
161
+ Utils::Time.extract_expiry_time(expiry),
162
+ options.to_backend)
163
+ GetResult.new do |res|
164
+ res.transcoder = options.transcoder
165
+ res.cas = resp[:cas]
166
+ res.flags = resp[:flags]
167
+ res.encoded = resp[:content]
168
+ end
169
+ end
170
+
171
+ # Reads from all available replicas and the active node and returns the results
172
+ #
173
+ # @param [String] id the document id which is used to uniquely identify it.
174
+ # @param [Options::GetAllReplicas] options request customization
175
+ #
176
+ # @return [Array<GetReplicaResult>]
177
+ def get_all_replicas(id, options = Options::GetAllReplicas::DEFAULT)
178
+ resp = @backend.document_get_all_replicas(@bucket_name, @scope_name, @name, id, options.to_backend)
179
+ resp.map do |entry|
180
+ GetReplicaResult.new do |res|
181
+ res.transcoder = options.transcoder
182
+ res.cas = entry[:cas]
183
+ res.flags = entry[:flags]
184
+ res.encoded = entry[:content]
185
+ res.is_replica = entry[:is_replica]
186
+ end
187
+ end
188
+ end
189
+
190
+ # Reads all available replicas and active, and returns the first found.
191
+ #
192
+ # @param [String] id the document id which is used to uniquely identify it.
193
+ # @param [Options::GetAnyReplica] options request customization
194
+ #
195
+ # @example Get document contents
196
+ # res = collection.get_any_replica("customer123")
197
+ # res.is_active #=> false
198
+ # res.content["addresses"]
199
+ #
200
+ # # {"billing"=>
201
+ # # {"line1"=>"123 Any Street", "line2"=>"Anytown", "country"=>"United Kingdom"},
202
+ # # "delivery"=>
203
+ # # {"line1"=>"123 Any Street", "line2"=>"Anytown", "country"=>"United Kingdom"}}
204
+ #
205
+ #
206
+ # @return [GetReplicaResult]
207
+ def get_any_replica(id, options = Options::GetAnyReplica::DEFAULT)
208
+ resp = @backend.document_get_any_replica(@bucket_name, @scope_name, @name, id, options.to_backend)
209
+ GetReplicaResult.new do |res|
210
+ res.transcoder = options.transcoder
211
+ res.cas = resp[:cas]
212
+ res.flags = resp[:flags]
213
+ res.encoded = resp[:content]
214
+ res.is_replica = resp[:is_replica]
215
+ end
216
+ end
217
+
218
+ # Checks if the given document ID exists on the active partition.
219
+ #
220
+ # @param [String] id the document id which is used to uniquely identify it.
221
+ # @param [Options::Exists] options request customization
222
+ #
223
+ # @example Check if the document exists without fetching its contents
224
+ # res = collection.exists("customer123")
225
+ # res.exists? #=> true
226
+ #
227
+ # @return [ExistsResult]
228
+ def exists(id, options = Options::Exists::DEFAULT)
229
+ resp = @backend.document_exists(bucket_name, @scope_name, @name, id, options.to_backend)
230
+ ExistsResult.new do |res|
231
+ res.deleted = resp[:deleted]
232
+ res.exists = resp[:exists]
233
+ res.expiry = resp[:expiry]
234
+ res.flags = resp[:flags]
235
+ res.sequence_number = resp[:sequence_number]
236
+ res.datatype = resp[:datatype]
237
+ res.cas = resp[:cas]
238
+ end
239
+ end
240
+
241
+ # Removes a document from the collection
242
+ #
243
+ # @param [String] id the document id which is used to uniquely identify it.
244
+ # @param [Options::Remove] options request customization
245
+ #
246
+ # @example Remove the document in collection
247
+ # res = collection.remove("customer123")
248
+ # res.cas #=> 241994216651798
249
+ #
250
+ # @example Remove the document in collection, but apply optimistic lock
251
+ # res = collection.upsert("mydoc", {"foo" => 42})
252
+ # res.cas #=> 7751414725654
253
+ #
254
+ # begin
255
+ # res = collection.remove("mydoc", Options::Remove(cas: 3735928559))
256
+ # rescue Error::CasMismatch
257
+ # puts "Failed to remove the document, it might be changed by other application"
258
+ # end
259
+ #
260
+ # @return [MutationResult]
261
+ def remove(id, options = Options::Remove::DEFAULT)
262
+ resp = @backend.document_remove(bucket_name, @scope_name, @name, id, options.to_backend)
263
+ MutationResult.new do |res|
264
+ res.cas = resp[:cas]
265
+ res.mutation_token = extract_mutation_token(resp)
266
+ end
267
+ end
268
+
269
+ # Removes a list of the documents from the collection
270
+ #
271
+ # @note that it will not generate {Error::DocumentNotFound} or {Error::CasMismatch} exceptions in this case.
272
+ # The caller should check {MutationResult#error} property of the result
273
+ #
274
+ # @param [Array<String, Array>] ids the array of document ids, or ID/CAS pairs +[String,Integer]+
275
+ # @param [Options::RemoveMulti] options request customization
276
+ #
277
+ # @example Remove two documents in collection. For "mydoc" apply optimistic lock
278
+ # res = collection.upsert("mydoc", {"foo" => 42})
279
+ # res.cas #=> 7751414725654
280
+ #
281
+ # res = collection.remove_multi(["foo", ["mydoc", res.cas]])
282
+ # if res[1].error.is_a?(Error::CasMismatch)
283
+ # puts "Failed to remove the document, it might be changed by other application"
284
+ # end
285
+ #
286
+ # @return [Array<MutationResult>]
287
+ def remove_multi(ids, options = Options::RemoveMulti::DEFAULT)
288
+ resp = @backend.document_remove_multi(bucket_name, @scope_name, @name, ids.map do |id|
289
+ case id
290
+ when String
291
+ [id, nil]
292
+ when Array
293
+ id
294
+ else
295
+ raise ArgumentError, "id argument of remove_multi must be a String or Array<String, Integer>, given: #{id.inspect}"
296
+ end
297
+ end, options.to_backend)
298
+ resp.map do |entry|
299
+ MutationResult.new do |res|
300
+ res.cas = entry[:cas]
301
+ res.mutation_token = extract_mutation_token(entry)
302
+ res.error = entry[:error]
303
+ res.id = entry[:id]
304
+ end
305
+ end
306
+ end
307
+
308
+ # Inserts a full document which does not exist yet
309
+ #
310
+ # @param [String] id the document id which is used to uniquely identify it.
311
+ # @param [Object] content the document content to insert
312
+ # @param [Options::Insert] options request customization
313
+ #
314
+ # @example Insert new document in collection
315
+ # res = collection.insert("mydoc", {"foo" => 42}, Options::Insert(expiry: 20))
316
+ # res.cas #=> 242287264414742
317
+ #
318
+ # @example Handle error when the document already exists
319
+ # collection.exists("mydoc").exists? #=> true
320
+ # begin
321
+ # res = collection.insert("mydoc", {"foo" => 42})
322
+ # rescue Error::DocumentExists
323
+ # puts "Failed to insert the document, it already exists in the collection"
324
+ # end
325
+ #
326
+ # @return [MutationResult]
327
+ def insert(id, content, options = Options::Insert::DEFAULT)
328
+ blob, flags = options.transcoder ? options.transcoder.encode(content) : [content, 0]
329
+ resp = @backend.document_insert(bucket_name, @scope_name, @name, id, blob, flags, options.to_backend)
330
+ MutationResult.new do |res|
331
+ res.cas = resp[:cas]
332
+ res.mutation_token = extract_mutation_token(resp)
333
+ end
334
+ end
335
+
336
+ # Upserts (inserts or updates) a full document which might or might not exist yet
337
+ #
338
+ # @param [String] id the document id which is used to uniquely identify it.
339
+ # @param [Object] content the document content to upsert
340
+ # @param [Options::Upsert] options request customization
341
+ #
342
+ # @example Upsert new document in collection
343
+ # res = collection.upsert("mydoc", {"foo" => 42}, Options::Upsert(expiry: 20))
344
+ # res.cas #=> 242287264414742
345
+ #
346
+ # @return [MutationResult]
347
+ def upsert(id, content, options = Options::Upsert::DEFAULT)
348
+ blob, flags = options.transcoder ? options.transcoder.encode(content) : [content, 0]
349
+ resp = @backend.document_upsert(bucket_name, @scope_name, @name, id, blob, flags, options.to_backend)
350
+ MutationResult.new do |res|
351
+ res.cas = resp[:cas]
352
+ res.mutation_token = extract_mutation_token(resp)
353
+ end
354
+ end
355
+
356
+ # Upserts (inserts or updates) a list of documents which might or might not exist yet
357
+ #
358
+ # @note that it will not generate exceptions in this case. The caller should check {MutationResult#error} property of the
359
+ # result
360
+ #
361
+ # @param [Array<Array>] id_content array of tuples +String,Object+, where first entry treated as document key,
362
+ # and the second as value to upsert.
363
+ # @param [Options::UpsertMulti] options request customization
364
+ #
365
+ # @example Upsert two documents with IDs "foo" and "bar" into a collection
366
+ # res = collection.upsert_multi([
367
+ # "foo", {"foo" => 42},
368
+ # "bar", {"bar" => "some value"}
369
+ # ])
370
+ # res[0].cas #=> 7751414725654
371
+ # res[1].cas #=> 7751418925851
372
+ #
373
+ # @return [Array<MutationResult>]
374
+ def upsert_multi(id_content, options = Options::UpsertMulti::DEFAULT)
375
+ resp = @backend.document_upsert_multi(bucket_name, @scope_name, @name, id_content.map do |(id, content)|
376
+ blob, flags = options.transcoder ? options.transcoder.encode(content) : [content, 0]
377
+ [id, blob, flags]
378
+ end, options.to_backend)
379
+ resp.map do |entry|
380
+ MutationResult.new do |res|
381
+ res.cas = entry[:cas]
382
+ res.mutation_token = extract_mutation_token(entry)
383
+ res.error = entry[:error]
384
+ res.id = entry[:id]
385
+ end
386
+ end
387
+ end
388
+
389
+ # Replaces a full document which already exists
390
+ #
391
+ # @param [String] id the document id which is used to uniquely identify it.
392
+ # @param [Object] content the document content to upsert
393
+ # @param [Options::Replace] options request customization
394
+ #
395
+ # @example Replace new document in collection with optimistic locking
396
+ # res = collection.get("mydoc")
397
+ # res = collection.replace("mydoc", {"foo" => 42}, Options::Replace(cas: res.cas))
398
+ # res.cas #=> 242287264414742
399
+ #
400
+ # @return [MutationResult]
401
+ def replace(id, content, options = Options::Replace::DEFAULT)
402
+ blob, flags = options.transcoder ? options.transcoder.encode(content) : [content, 0]
403
+ resp = @backend.document_replace(bucket_name, @scope_name, @name, id, blob, flags, options.to_backend)
404
+ MutationResult.new do |res|
405
+ res.cas = resp[:cas]
406
+ res.mutation_token = extract_mutation_token(resp)
407
+ end
408
+ end
409
+
410
+ # Update the expiration of the document with the given id
411
+ #
412
+ # @param [String] id the document id which is used to uniquely identify it.
413
+ # @param [Integer, #in_seconds, Time] expiry new expiration time for the document
414
+ # @param [Options::Touch] options request customization
415
+ #
416
+ # @example Reset expiration timer for document to 30 seconds
417
+ # res = collection.touch("customer123", 30)
418
+ #
419
+ # @return [MutationResult]
420
+ def touch(id, expiry, options = Options::Touch::DEFAULT)
421
+ resp = @backend.document_touch(bucket_name, @scope_name, @name, id,
422
+ Utils::Time.extract_expiry_time(expiry),
423
+ options.to_backend)
424
+ MutationResult.new do |res|
425
+ res.cas = resp[:cas]
426
+ end
427
+ end
428
+
429
+ # Unlocks a document if it has been locked previously
430
+ #
431
+ # @param [String] id the document id which is used to uniquely identify it.
432
+ # @param [Integer] cas CAS value which is needed to unlock the document
433
+ # @param [Options::Unlock] options request customization
434
+ #
435
+ # @example Lock (pessimistically) and unlock document
436
+ # res = collection.get_and_lock("customer123", 10)
437
+ # collection.unlock("customer123", res.cas)
438
+ #
439
+ # @return [void]
440
+ #
441
+ # @raise [Error::DocumentNotFound]
442
+ def unlock(id, cas, options = Options::Unlock::DEFAULT)
443
+ @backend.document_unlock(bucket_name, @scope_name, @name, id, cas, options.to_backend)
444
+ end
445
+
446
+ # Performs lookups to document fragments
447
+ #
448
+ # @param [String] id the document id which is used to uniquely identify it.
449
+ # @param [Array<LookupInSpec>] specs the list of specifications which describe the types of the lookups to perform
450
+ # @param [Options::LookupIn] options request customization
451
+ #
452
+ # @example Get list of IDs of completed purchases
453
+ # lookup_specs = [
454
+ # LookupInSpec::get("purchases.complete")
455
+ # ]
456
+ # collection.lookup_in("customer123", lookup_specs)
457
+ #
458
+ # @example Retrieve country name and check if pending purchases array is empty
459
+ # collection.lookup_in "customer123", [
460
+ # LookupInSpec.get("addresses.delivery.country"),
461
+ # LookupInSpec.exists("purchases.pending[-1]"),
462
+ # ]
463
+ # @return [LookupInResult]
464
+ def lookup_in(id, specs, options = Options::LookupIn::DEFAULT)
465
+ resp = @backend.document_lookup_in(
466
+ bucket_name, @scope_name, @name, id,
467
+ specs.map do |s|
468
+ {
469
+ opcode: s.type,
470
+ xattr: s.xattr?,
471
+ path: s.path,
472
+ }
473
+ end, options.to_backend
474
+ )
475
+ LookupInResult.new do |res|
476
+ res.transcoder = options.transcoder
477
+ res.cas = resp[:cas]
478
+ res.deleted = resp[:deleted]
479
+ res.encoded = resp[:fields].map do |field|
480
+ SubDocumentField.new do |f|
481
+ f.exists = field[:exists]
482
+ f.index = field[:index]
483
+ f.path = field[:path]
484
+ f.value = field[:value]
485
+ f.error = field[:error]
486
+ end
487
+ end
488
+ end
489
+ end
490
+
491
+ # Performs lookups to document fragments. Reads from the active node and all available replicas and returns the
492
+ # first result found
493
+ #
494
+ # @param [String] id the document id which is used to uniquely identify it.
495
+ # @param [Array<LookupInSpec>] specs the list of specifications which describe the types of the lookups to perform
496
+ # @param [Options::LookupInAnyReplica] options request customization
497
+ #
498
+ # @return [LookupInReplicaResult]
499
+ #
500
+ # @raise [Error::DocumentIrretrievable]
501
+ # @raise [Error::Timeout]
502
+ # @raise [Error::CouchbaseError]
503
+ # @raise [Error::FeatureNotAvailable]
504
+ def lookup_in_any_replica(id, specs, options = Options::LookupInAnyReplica::DEFAULT)
505
+ resp = @backend.document_lookup_in_any_replica(
506
+ bucket_name, @scope_name, @name, id,
507
+ specs.map do |s|
508
+ {
509
+ opcode: s.type,
510
+ xattr: s.xattr?,
511
+ path: s.path,
512
+ }
513
+ end, options.to_backend
514
+ )
515
+ extract_lookup_in_replica_result(resp, options)
516
+ end
517
+
518
+ # Performs lookups to document fragments. Reads from the active node and all available replicas and returns all of
519
+ # the results
520
+ #
521
+ # @param [String] id the document id which is used to uniquely identify it.
522
+ # @param [Array<LookupInSpec>] specs the list of specifications which describe the types of the lookups to perform
523
+ # @param [Options::LookupInAllReplicas] options request customization
524
+ #
525
+ # @return [Array<LookupInReplicaResult>]
526
+ #
527
+ # @raise [Error::DocumentNotFound]
528
+ # @raise [Error::Timeout]
529
+ # @raise [Error::CouchbaseError]
530
+ # @raise [Error::FeatureNotAvailable]
531
+ def lookup_in_all_replicas(id, specs, options = Options::LookupInAllReplicas::DEFAULT)
532
+ resp = @backend.document_lookup_in_all_replicas(
533
+ bucket_name, @scope_name, @name, id,
534
+ specs.map do |s|
535
+ {
536
+ opcode: s.type,
537
+ xattr: s.xattr?,
538
+ path: s.path,
539
+ }
540
+ end, options.to_backend
541
+ )
542
+ resp.map do |entry|
543
+ extract_lookup_in_replica_result(entry, options)
544
+ end
545
+ end
546
+
547
+ # Performs mutations to document fragments
548
+ #
549
+ # @param [String] id the document id which is used to uniquely identify it.
550
+ # @param [Array<MutateInSpec>] specs the list of specifications which describe the types of the lookups to perform
551
+ # @param [Options::MutateIn] options request customization
552
+ #
553
+ # @example Append number into subarray of the document
554
+ # mutation_specs = [
555
+ # MutateInSpec::array_append("purchases.complete", [42])
556
+ # ]
557
+ # collection.mutate_in("customer123", mutation_specs, Options::MutateIn(expiry: 10))
558
+ #
559
+ # @example Write meta attribute, remove array entry and replace email field
560
+ # collection.mutate_in("customer123", [
561
+ # MutateInSpec.upsert("_framework.model_type", "Customer").xattr,
562
+ # MutateInSpec.remove("addresses.billing[2]"),
563
+ # MutateInSpec.replace("email", "dougr96@hotmail.com"),
564
+ # ])
565
+ #
566
+ # @return [MutateInResult]
567
+ def mutate_in(id, specs, options = Options::MutateIn::DEFAULT)
568
+ resp = @backend.document_mutate_in(
569
+ bucket_name, @scope_name, @name, id,
570
+ specs.map do |s|
571
+ {
572
+ opcode: s.type,
573
+ path: s.path,
574
+ param: s.param,
575
+ xattr: s.xattr?,
576
+ expand_macros: s.expand_macros?,
577
+ create_path: s.create_path?,
578
+ }
579
+ end, options.to_backend
580
+ )
581
+ MutateInResult.new do |res|
582
+ res.transcoder = options.transcoder
583
+ res.cas = resp[:cas]
584
+ res.deleted = resp[:deleted]
585
+ res.mutation_token = extract_mutation_token(resp)
586
+ res.encoded = resp[:fields].map do |field|
587
+ SubDocumentField.new do |f|
588
+ f.index = field[:index]
589
+ f.path = field[:path]
590
+ f.value = field[:value]
591
+ end
592
+ end
593
+ end
594
+ end
595
+
596
+ # Performs a key-value scan operation on the collection
597
+ #
598
+ # @param [RangeScan, PrefixScan, SamplingScan] scan_type the type of the scan
599
+ # @param [Options::Scan] options request customization
600
+ #
601
+ # @return [ScanResults]
602
+ #
603
+ # @example Get a sample of up to 5 documents from the collection and store their IDs in an array
604
+ # result = collection.scan(SamplingScan.new(5), Options::Scan.new(ids_only: true))
605
+ # ids = result.map { |item| item.id }
606
+ #
607
+ # @example Get all documents whose ID starts with 'customer_1' and output their content
608
+ # result = collection.scan(PrefixScan.new("customer_1"))
609
+ # result.each { |item| puts item.content }
610
+ #
611
+ # @example Get all documents with ID between 'customer_1' and 'customer_2', excluding 'customer_2' and output their content
612
+ # result = collection.scan(RangeScan.new(
613
+ # from: ScanTerm.new("customer_1"),
614
+ # to: ScanTerm.new("customer_2", exclusive: true)
615
+ # ))
616
+ # result.each { |item| puts item.content }
617
+ #
618
+ # @note
619
+ # Use this API for low concurrency batch queries where latency is not a critical as the system may have to scan
620
+ # a lot of documents to find the matching documents. For low latency range queries, it is recommended that you use
621
+ # SQL++ with the necessary indexes.
622
+ def scan(scan_type, options = Options::Scan::DEFAULT)
623
+ ScanResults.new(
624
+ core_scan_result: @backend.document_scan_create(
625
+ @bucket_name, @scope_name, @name, scan_type.to_backend, options.to_backend
626
+ ),
627
+ transcoder: options.transcoder
628
+ )
629
+ end
630
+
631
+ private
632
+
633
+ def extract_mutation_token(resp)
634
+ return unless resp.key?(:mutation_token)
635
+
636
+ MutationToken.new do |token|
637
+ token.partition_id = resp[:mutation_token][:partition_id]
638
+ token.partition_uuid = resp[:mutation_token][:partition_uuid]
639
+ token.sequence_number = resp[:mutation_token][:sequence_number]
640
+ token.bucket_name = resp[:mutation_token][:bucket_name]
641
+ end
642
+ end
643
+
644
+ def extract_lookup_in_replica_result(resp, options)
645
+ LookupInReplicaResult.new do |res|
646
+ res.transcoder = options.transcoder
647
+ res.cas = resp[:cas]
648
+ res.deleted = resp[:deleted]
649
+ res.is_replica = resp[:is_replica]
650
+ res.encoded = resp[:fields].map do |field|
651
+ SubDocumentField.new do |f|
652
+ f.exists = field[:exists]
653
+ f.index = field[:index]
654
+ f.path = field[:path]
655
+ f.value = field[:value]
656
+ f.error = field[:error]
657
+ end
658
+ end
659
+ end
660
+ end
661
+
662
+ # @api private
663
+ # TODO: deprecate in 3.1
664
+ GetOptions = ::Couchbase::Options::Get
665
+ # @api private
666
+ # TODO: deprecate in 3.1
667
+ GetAndLockOptions = ::Couchbase::Options::GetAndLock
668
+ # @api private
669
+ # TODO: deprecate in 3.1
670
+ GetAndTouchOptions = ::Couchbase::Options::GetAndTouch
671
+ # @api private
672
+ # TODO: deprecate in 3.1
673
+ LookupInOptions = ::Couchbase::Options::LookupIn
674
+ # @api private
675
+ # TODO: deprecate in 3.1
676
+ MutateInOptions = ::Couchbase::Options::MutateIn
677
+ # @api private
678
+ # TODO: deprecate in 3.1
679
+ UnlockOptions = ::Couchbase::Options::Unlock
680
+ # @api private
681
+ # TODO: deprecate in 3.1
682
+ TouchOptions = ::Couchbase::Options::Touch
683
+ # @api private
684
+ # TODO: deprecate in 3.1
685
+ ReplaceOptions = ::Couchbase::Options::Replace
686
+ # @api private
687
+ # TODO: deprecate in 3.1
688
+ UpsertOptions = ::Couchbase::Options::Upsert
689
+ # @api private
690
+ # TODO: deprecate in 3.1
691
+ InsertOptions = ::Couchbase::Options::Insert
692
+ # @api private
693
+ # TODO: deprecate in 3.1
694
+ RemoveOptions = ::Couchbase::Options::Remove
695
+ # @api private
696
+ # TODO: deprecate in 3.1
697
+ ExistsOptions = ::Couchbase::Options::Exists
698
+ # @api private
699
+ # TODO: deprecate in 3.1
700
+ GetAnyReplicaOptions = ::Couchbase::Options::GetAnyReplica
701
+ # @api private
702
+ # TODO: deprecate in 3.1
703
+ GetAllReplicasOptions = ::Couchbase::Options::GetAllReplicas
704
+ end
705
+ end