couchbase 3.4.0-arm64-darwin-20

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 (50) 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/analytics_options.rb +107 -0
  7. data/lib/couchbase/authenticator.rb +65 -0
  8. data/lib/couchbase/binary_collection.rb +128 -0
  9. data/lib/couchbase/binary_collection_options.rb +24 -0
  10. data/lib/couchbase/bucket.rb +144 -0
  11. data/lib/couchbase/cluster.rb +439 -0
  12. data/lib/couchbase/cluster_registry.rb +44 -0
  13. data/lib/couchbase/collection.rb +589 -0
  14. data/lib/couchbase/collection_options.rb +300 -0
  15. data/lib/couchbase/config_profiles.rb +55 -0
  16. data/lib/couchbase/configuration.rb +57 -0
  17. data/lib/couchbase/datastructures/couchbase_list.rb +160 -0
  18. data/lib/couchbase/datastructures/couchbase_map.rb +194 -0
  19. data/lib/couchbase/datastructures/couchbase_queue.rb +134 -0
  20. data/lib/couchbase/datastructures/couchbase_set.rb +128 -0
  21. data/lib/couchbase/datastructures.rb +24 -0
  22. data/lib/couchbase/diagnostics.rb +181 -0
  23. data/lib/couchbase/errors.rb +351 -0
  24. data/lib/couchbase/json_transcoder.rb +32 -0
  25. data/lib/couchbase/libcouchbase.bundle +0 -0
  26. data/lib/couchbase/logger.rb +85 -0
  27. data/lib/couchbase/management/analytics_index_manager.rb +1127 -0
  28. data/lib/couchbase/management/bucket_manager.rb +436 -0
  29. data/lib/couchbase/management/collection_manager.rb +321 -0
  30. data/lib/couchbase/management/query_index_manager.rb +520 -0
  31. data/lib/couchbase/management/search_index_manager.rb +408 -0
  32. data/lib/couchbase/management/user_manager.rb +468 -0
  33. data/lib/couchbase/management/view_index_manager.rb +237 -0
  34. data/lib/couchbase/management.rb +27 -0
  35. data/lib/couchbase/mutation_state.rb +63 -0
  36. data/lib/couchbase/options.rb +2580 -0
  37. data/lib/couchbase/query_options.rb +120 -0
  38. data/lib/couchbase/railtie.rb +45 -0
  39. data/lib/couchbase/scope.rb +232 -0
  40. data/lib/couchbase/search_options.rb +1570 -0
  41. data/lib/couchbase/subdoc.rb +290 -0
  42. data/lib/couchbase/utils/generic_logger_adapter.rb +38 -0
  43. data/lib/couchbase/utils/stdlib_logger_adapter.rb +65 -0
  44. data/lib/couchbase/utils/time.rb +56 -0
  45. data/lib/couchbase/utils.rb +21 -0
  46. data/lib/couchbase/version.rb +23 -0
  47. data/lib/couchbase/view_options.rb +65 -0
  48. data/lib/couchbase.rb +20 -0
  49. data/lib/rails/generators/couchbase/config/config_generator.rb +27 -0
  50. metadata +101 -0
@@ -0,0 +1,589 @@
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
+
19
+ module Couchbase
20
+ # Provides access to all collection APIs
21
+ class Collection
22
+ attr_reader :bucket_name
23
+ attr_reader :scope_name
24
+ attr_reader :name
25
+
26
+ alias inspect to_s
27
+
28
+ # @param [Couchbase::Backend] backend
29
+ # @param [String] bucket_name name of the bucket
30
+ # @param [String] scope_name name of the scope
31
+ # @param [String] collection_name name of the collection
32
+ def initialize(backend, bucket_name, scope_name, collection_name)
33
+ @backend = backend
34
+ @bucket_name = bucket_name
35
+ @scope_name = scope_name
36
+ @name = collection_name
37
+ end
38
+
39
+ # Provides access to the binary APIs, not used for JSON documents
40
+ #
41
+ # @return [BinaryCollection]
42
+ def binary
43
+ BinaryCollection.new(self)
44
+ end
45
+
46
+ # Fetches the full document from the collection
47
+ #
48
+ # @param [String] id the document id which is used to uniquely identify it
49
+ # @param [Options::Get] options request customization
50
+ #
51
+ # @example Get document contents
52
+ # res = collection.get("customer123")
53
+ # res.content["addresses"]
54
+ #
55
+ # # {"billing"=>
56
+ # # {"line1"=>"123 Any Street", "line2"=>"Anytown", "country"=>"United Kingdom"},
57
+ # # "delivery"=>
58
+ # # {"line1"=>"123 Any Street", "line2"=>"Anytown", "country"=>"United Kingdom"}}
59
+ #
60
+ # @example Get partial document using projections
61
+ # res = collection.get("customer123", Options::Get(projections: ["name", "addresses.billing"]))
62
+ # res.content
63
+ #
64
+ # # {"addresses"=>
65
+ # # {"billing"=>
66
+ # # {"country"=>"United Kingdom",
67
+ # # "line1"=>"123 Any Street",
68
+ # # "line2"=>"Anytown"}},
69
+ # # "name"=>"Douglas Reynholm"}
70
+ #
71
+ # @return [GetResult]
72
+ def get(id, options = Options::Get::DEFAULT)
73
+ resp = if options.need_projected_get?
74
+ @backend.document_get_projected(bucket_name, @scope_name, @name, id, options.to_backend)
75
+ else
76
+ @backend.document_get(bucket_name, @scope_name, @name, id, options.to_backend)
77
+ end
78
+ GetResult.new do |res|
79
+ res.transcoder = options.transcoder
80
+ res.cas = resp[:cas]
81
+ res.flags = resp[:flags]
82
+ res.encoded = resp[:content]
83
+ res.expiry = resp[:expiry] if resp.key?(:expiry)
84
+ end
85
+ end
86
+
87
+ # Fetches multiple documents from the collection.
88
+ #
89
+ # @note that it will not generate {Error::DocumentNotFound} exceptions in this case. The caller should check
90
+ # {GetResult#error} property of the result
91
+ #
92
+ # @param [Array<String>] ids the array of document identifiers
93
+ # @param [Options::GetMulti] options request customization
94
+ #
95
+ # @example Fetch "foo" and "bar" in a batch
96
+ # res = collection.get(["foo", "bar"], Options::GetMulti(timeout: 3_000))
97
+ # res[0].content #=> content of "foo"
98
+ # res[1].content #=> content of "bar"
99
+ #
100
+ # @return [Array<GetResult>]
101
+ def get_multi(ids, options = Options::GetMulti::DEFAULT)
102
+ resp = @backend.document_get_multi(ids.map { |id| [bucket_name, @scope_name, @name, id] }, options.to_backend)
103
+ resp.map do |entry|
104
+ GetResult.new do |res|
105
+ res.transcoder = options.transcoder
106
+ res.id = entry[:id]
107
+ res.cas = entry[:cas]
108
+ res.flags = entry[:flags]
109
+ res.encoded = entry[:content]
110
+ res.error = entry[:error]
111
+ end
112
+ end
113
+ end
114
+
115
+ # Fetches the full document and write-locks it for the given duration
116
+ #
117
+ # @param [String] id the document id which is used to uniquely identify it.
118
+ # @param [Integer, #in_seconds] lock_time how long to lock the document (values over 30 seconds will be capped)
119
+ # @param [Options::GetAndLock] options request customization
120
+ #
121
+ # @example Retrieve document and lock for 10 seconds
122
+ # collection.get_and_lock("customer123", 10, Options::GetAndLock(timeout: 3_000))
123
+ #
124
+ # @example Update document pessimistically
125
+ # res = collection.get_and_lock("customer123", 10)
126
+ # user_data = res.content
127
+ # user_data["admin"] = true
128
+ # collection.replace("user", user_data, Options::Upsert(cas: res.cas))
129
+ #
130
+ # @return [GetResult]
131
+ def get_and_lock(id, lock_time, options = Options::GetAndLock::DEFAULT)
132
+ resp = @backend.document_get_and_lock(bucket_name, @scope_name, @name, id,
133
+ lock_time.respond_to?(:in_seconds) ? lock_time.public_send(:in_seconds) : lock_time,
134
+ options.to_backend)
135
+ GetResult.new do |res|
136
+ res.transcoder = options.transcoder
137
+ res.cas = resp[:cas]
138
+ res.flags = resp[:flags]
139
+ res.encoded = resp[:content]
140
+ end
141
+ end
142
+
143
+ # Fetches a full document and resets its expiration time to the duration provided
144
+ #
145
+ # @param [String] id the document id which is used to uniquely identify it.
146
+ # @param [Integer, #in_seconds, Time] expiry the new expiration time for the document
147
+ # @param [Options::GetAndTouch] options request customization
148
+ #
149
+ # @example Retrieve document and prolong its expiration for another 10 seconds
150
+ # collection.get_and_touch("customer123", 10)
151
+ #
152
+ # @return [GetResult]
153
+ def get_and_touch(id, expiry, options = Options::GetAndTouch::DEFAULT)
154
+ resp = @backend.document_get_and_touch(bucket_name, @scope_name, @name, id,
155
+ Utils::Time.extract_expiry_time(expiry),
156
+ options.to_backend)
157
+ GetResult.new do |res|
158
+ res.transcoder = options.transcoder
159
+ res.cas = resp[:cas]
160
+ res.flags = resp[:flags]
161
+ res.encoded = resp[:content]
162
+ end
163
+ end
164
+
165
+ # Reads from all available replicas and the active node and returns the results
166
+ #
167
+ # @param [String] id the document id which is used to uniquely identify it.
168
+ # @param [Options::GetAllReplicas] options request customization
169
+ #
170
+ # @return [Array<GetReplicaResult>]
171
+ def get_all_replicas(id, options = Options::GetAllReplicas::DEFAULT)
172
+ resp = @backend.document_get_all_replicas(@bucket_name, @scope_name, @name, id, options.to_backend)
173
+ resp.map do |entry|
174
+ GetReplicaResult.new do |res|
175
+ res.transcoder = options.transcoder
176
+ res.cas = entry[:cas]
177
+ res.flags = entry[:flags]
178
+ res.encoded = entry[:content]
179
+ res.is_replica = entry[:is_replica]
180
+ end
181
+ end
182
+ end
183
+
184
+ # Reads all available replicas and active, and returns the first found.
185
+ #
186
+ # @param [String] id the document id which is used to uniquely identify it.
187
+ # @param [Options::GetAnyReplica] options request customization
188
+ #
189
+ # @example Get document contents
190
+ # res = collection.get_any_replica("customer123")
191
+ # res.is_active #=> false
192
+ # res.content["addresses"]
193
+ #
194
+ # # {"billing"=>
195
+ # # {"line1"=>"123 Any Street", "line2"=>"Anytown", "country"=>"United Kingdom"},
196
+ # # "delivery"=>
197
+ # # {"line1"=>"123 Any Street", "line2"=>"Anytown", "country"=>"United Kingdom"}}
198
+ #
199
+ #
200
+ # @return [GetReplicaResult]
201
+ def get_any_replica(id, options = Options::GetAnyReplica::DEFAULT)
202
+ resp = @backend.document_get_any_replica(@bucket_name, @scope_name, @name, id, options.to_backend)
203
+ GetReplicaResult.new do |res|
204
+ res.transcoder = options.transcoder
205
+ res.cas = resp[:cas]
206
+ res.flags = resp[:flags]
207
+ res.encoded = resp[:content]
208
+ res.is_replica = resp[:is_replica]
209
+ end
210
+ end
211
+
212
+ # Checks if the given document ID exists on the active partition.
213
+ #
214
+ # @param [String] id the document id which is used to uniquely identify it.
215
+ # @param [Options::Exists] options request customization
216
+ #
217
+ # @example Check if the document exists without fetching its contents
218
+ # res = collection.exists("customer123")
219
+ # res.exists? #=> true
220
+ #
221
+ # @return [ExistsResult]
222
+ def exists(id, options = Options::Exists::DEFAULT)
223
+ resp = @backend.document_exists(bucket_name, @scope_name, @name, id, options.to_backend)
224
+ ExistsResult.new do |res|
225
+ res.deleted = resp[:deleted]
226
+ res.exists = resp[:exists]
227
+ res.expiry = resp[:expiry]
228
+ res.flags = resp[:flags]
229
+ res.sequence_number = resp[:sequence_number]
230
+ res.datatype = resp[:datatype]
231
+ res.cas = resp[:cas]
232
+ end
233
+ end
234
+
235
+ # Removes a document from the collection
236
+ #
237
+ # @param [String] id the document id which is used to uniquely identify it.
238
+ # @param [Options::Remove] options request customization
239
+ #
240
+ # @example Remove the document in collection
241
+ # res = collection.remove("customer123")
242
+ # res.cas #=> 241994216651798
243
+ #
244
+ # @example Remove the document in collection, but apply optimistic lock
245
+ # res = collection.upsert("mydoc", {"foo" => 42})
246
+ # res.cas #=> 7751414725654
247
+ #
248
+ # begin
249
+ # res = collection.remove("mydoc", Options::Remove(cas: 3735928559))
250
+ # rescue Error::CasMismatch
251
+ # puts "Failed to remove the document, it might be changed by other application"
252
+ # end
253
+ #
254
+ # @return [MutationResult]
255
+ def remove(id, options = Options::Remove::DEFAULT)
256
+ resp = @backend.document_remove(bucket_name, @scope_name, @name, id, options.to_backend)
257
+ MutationResult.new do |res|
258
+ res.cas = resp[:cas]
259
+ res.mutation_token = extract_mutation_token(resp)
260
+ end
261
+ end
262
+
263
+ # Removes a list of the documents from the collection
264
+ #
265
+ # @note that it will not generate {Error::DocumentNotFound} or {Error::CasMismatch} exceptions in this case.
266
+ # The caller should check {MutationResult#error} property of the result
267
+ #
268
+ # @param [Array<String, Array>] ids the array of document ids, or ID/CAS pairs +[String,Integer]+
269
+ # @param [Options::RemoveMulti] options request customization
270
+ #
271
+ # @example Remove two documents in collection. For "mydoc" apply optimistic lock
272
+ # res = collection.upsert("mydoc", {"foo" => 42})
273
+ # res.cas #=> 7751414725654
274
+ #
275
+ # res = collection.remove_multi(["foo", ["mydoc", res.cas]])
276
+ # if res[1].error.is_a?(Error::CasMismatch)
277
+ # puts "Failed to remove the document, it might be changed by other application"
278
+ # end
279
+ #
280
+ # @return [Array<MutationResult>]
281
+ def remove_multi(ids, options = Options::RemoveMulti::DEFAULT)
282
+ resp = @backend.document_remove_multi(bucket_name, @scope_name, @name, ids.map do |id|
283
+ case id
284
+ when String
285
+ [id, nil]
286
+ when Array
287
+ id
288
+ else
289
+ raise ArgumentError, "id argument of remove_multi must be a String or Array<String, Integer>, given: #{id.inspect}"
290
+ end
291
+ end, options.to_backend)
292
+ resp.map do |entry|
293
+ MutationResult.new do |res|
294
+ res.cas = entry[:cas]
295
+ res.mutation_token = extract_mutation_token(entry)
296
+ res.error = entry[:error]
297
+ res.id = entry[:id]
298
+ end
299
+ end
300
+ end
301
+
302
+ # Inserts a full document which does not exist yet
303
+ #
304
+ # @param [String] id the document id which is used to uniquely identify it.
305
+ # @param [Object] content the document content to insert
306
+ # @param [Options::Insert] options request customization
307
+ #
308
+ # @example Insert new document in collection
309
+ # res = collection.insert("mydoc", {"foo" => 42}, Options::Insert(expiry: 20))
310
+ # res.cas #=> 242287264414742
311
+ #
312
+ # @example Handle error when the document already exists
313
+ # collection.exists("mydoc").exists? #=> true
314
+ # begin
315
+ # res = collection.insert("mydoc", {"foo" => 42})
316
+ # rescue Error::DocumentExists
317
+ # puts "Failed to insert the document, it already exists in the collection"
318
+ # end
319
+ #
320
+ # @return [MutationResult]
321
+ def insert(id, content, options = Options::Insert::DEFAULT)
322
+ blob, flags = options.transcoder ? options.transcoder.encode(content) : [content, 0]
323
+ resp = @backend.document_insert(bucket_name, @scope_name, @name, id, blob, flags, options.to_backend)
324
+ MutationResult.new do |res|
325
+ res.cas = resp[:cas]
326
+ res.mutation_token = extract_mutation_token(resp)
327
+ end
328
+ end
329
+
330
+ # Upserts (inserts or updates) a full document which might or might not exist yet
331
+ #
332
+ # @param [String] id the document id which is used to uniquely identify it.
333
+ # @param [Object] content the document content to upsert
334
+ # @param [Options::Upsert] options request customization
335
+ #
336
+ # @example Upsert new document in collection
337
+ # res = collection.upsert("mydoc", {"foo" => 42}, Options::Upsert(expiry: 20))
338
+ # res.cas #=> 242287264414742
339
+ #
340
+ # @return [MutationResult]
341
+ def upsert(id, content, options = Options::Upsert::DEFAULT)
342
+ blob, flags = options.transcoder ? options.transcoder.encode(content) : [content, 0]
343
+ resp = @backend.document_upsert(bucket_name, @scope_name, @name, id, blob, flags, options.to_backend)
344
+ MutationResult.new do |res|
345
+ res.cas = resp[:cas]
346
+ res.mutation_token = extract_mutation_token(resp)
347
+ end
348
+ end
349
+
350
+ # Upserts (inserts or updates) a list of documents which might or might not exist yet
351
+ #
352
+ # @note that it will not generate exceptions in this case. The caller should check {MutationResult#error} property of the
353
+ # result
354
+ #
355
+ # @param [Array<Array>] id_content array of tuples +String,Object+, where first entry treated as document key,
356
+ # and the second as value to upsert.
357
+ # @param [Options::UpsertMulti] options request customization
358
+ #
359
+ # @example Upsert two documents with IDs "foo" and "bar" into a collection
360
+ # res = collection.upsert_multi([
361
+ # "foo", {"foo" => 42},
362
+ # "bar", {"bar" => "some value"}
363
+ # ])
364
+ # res[0].cas #=> 7751414725654
365
+ # res[1].cas #=> 7751418925851
366
+ #
367
+ # @return [Array<MutationResult>]
368
+ def upsert_multi(id_content, options = Options::UpsertMulti::DEFAULT)
369
+ resp = @backend.document_upsert_multi(bucket_name, @scope_name, @name, id_content.map do |(id, content)|
370
+ blob, flags = options.transcoder ? options.transcoder.encode(content) : [content, 0]
371
+ [id, blob, flags]
372
+ end, options.to_backend)
373
+ resp.map do |entry|
374
+ MutationResult.new do |res|
375
+ res.cas = entry[:cas]
376
+ res.mutation_token = extract_mutation_token(entry)
377
+ res.error = entry[:error]
378
+ res.id = entry[:id]
379
+ end
380
+ end
381
+ end
382
+
383
+ # Replaces a full document which already exists
384
+ #
385
+ # @param [String] id the document id which is used to uniquely identify it.
386
+ # @param [Object] content the document content to upsert
387
+ # @param [Options::Replace] options request customization
388
+ #
389
+ # @example Replace new document in collection with optimistic locking
390
+ # res = collection.get("mydoc")
391
+ # res = collection.replace("mydoc", {"foo" => 42}, Options::Replace(cas: res.cas))
392
+ # res.cas #=> 242287264414742
393
+ #
394
+ # @return [MutationResult]
395
+ def replace(id, content, options = Options::Replace::DEFAULT)
396
+ blob, flags = options.transcoder ? options.transcoder.encode(content) : [content, 0]
397
+ resp = @backend.document_replace(bucket_name, @scope_name, @name, id, blob, flags, options.to_backend)
398
+ MutationResult.new do |res|
399
+ res.cas = resp[:cas]
400
+ res.mutation_token = extract_mutation_token(resp)
401
+ end
402
+ end
403
+
404
+ # Update the expiration of the document with the given id
405
+ #
406
+ # @param [String] id the document id which is used to uniquely identify it.
407
+ # @param [Integer, #in_seconds, Time] expiry new expiration time for the document
408
+ # @param [Options::Touch] options request customization
409
+ #
410
+ # @example Reset expiration timer for document to 30 seconds
411
+ # res = collection.touch("customer123", 30)
412
+ #
413
+ # @return [MutationResult]
414
+ def touch(id, expiry, options = Options::Touch::DEFAULT)
415
+ resp = @backend.document_touch(bucket_name, @scope_name, @name, id,
416
+ Utils::Time.extract_expiry_time(expiry),
417
+ options.to_backend)
418
+ MutationResult.new do |res|
419
+ res.cas = resp[:cas]
420
+ end
421
+ end
422
+
423
+ # Unlocks a document if it has been locked previously
424
+ #
425
+ # @param [String] id the document id which is used to uniquely identify it.
426
+ # @param [Integer] cas CAS value which is needed to unlock the document
427
+ # @param [Options::Unlock] options request customization
428
+ #
429
+ # @example Lock (pessimistically) and unlock document
430
+ # res = collection.get_and_lock("customer123", 10)
431
+ # collection.unlock("customer123", res.cas)
432
+ #
433
+ # @return [void]
434
+ #
435
+ # @raise [Error::DocumentNotFound]
436
+ def unlock(id, cas, options = Options::Unlock::DEFAULT)
437
+ @backend.document_unlock(bucket_name, @scope_name, @name, id, cas, options.to_backend)
438
+ end
439
+
440
+ # Performs lookups to document fragments
441
+ #
442
+ # @param [String] id the document id which is used to uniquely identify it.
443
+ # @param [Array<LookupInSpec>] specs the list of specifications which describe the types of the lookups to perform
444
+ # @param [Options::LookupIn] options request customization
445
+ #
446
+ # @example Get list of IDs of completed purchases
447
+ # lookup_specs = [
448
+ # LookupInSpec::get("purchases.complete")
449
+ # ]
450
+ # collection.lookup_in("customer123", lookup_specs)
451
+ #
452
+ # @example Retrieve country name and check if pending purchases array is empty
453
+ # collection.lookup_in "customer123", [
454
+ # LookupInSpec.get("addresses.delivery.country"),
455
+ # LookupInSpec.exists("purchases.pending[-1]"),
456
+ # ]
457
+ # @return [LookupInResult]
458
+ def lookup_in(id, specs, options = Options::LookupIn::DEFAULT)
459
+ resp = @backend.document_lookup_in(
460
+ bucket_name, @scope_name, @name, id,
461
+ specs.map do |s|
462
+ {
463
+ opcode: s.type,
464
+ xattr: s.xattr?,
465
+ path: s.path,
466
+ }
467
+ end, options.to_backend
468
+ )
469
+ LookupInResult.new do |res|
470
+ res.transcoder = options.transcoder
471
+ res.cas = resp[:cas]
472
+ res.deleted = resp[:deleted]
473
+ res.encoded = resp[:fields].map do |field|
474
+ SubDocumentField.new do |f|
475
+ f.exists = field[:exists]
476
+ f.index = field[:index]
477
+ f.path = field[:path]
478
+ f.value = field[:value]
479
+ end
480
+ end
481
+ end
482
+ end
483
+
484
+ # Performs mutations to document fragments
485
+ #
486
+ # @param [String] id the document id which is used to uniquely identify it.
487
+ # @param [Array<MutateInSpec>] specs the list of specifications which describe the types of the lookups to perform
488
+ # @param [Options::MutateIn] options request customization
489
+ #
490
+ # @example Append number into subarray of the document
491
+ # mutation_specs = [
492
+ # MutateInSpec::array_append("purchases.complete", [42])
493
+ # ]
494
+ # collection.mutate_in("customer123", mutation_specs, Options::MutateIn(expiry: 10))
495
+ #
496
+ # @example Write meta attribute, remove array entry and replace email field
497
+ # collection.mutate_in("customer123", [
498
+ # MutateInSpec.upsert("_framework.model_type", "Customer").xattr,
499
+ # MutateInSpec.remove("addresses.billing[2]"),
500
+ # MutateInSpec.replace("email", "dougr96@hotmail.com"),
501
+ # ])
502
+ #
503
+ # @return [MutateInResult]
504
+ def mutate_in(id, specs, options = Options::MutateIn::DEFAULT)
505
+ resp = @backend.document_mutate_in(
506
+ bucket_name, @scope_name, @name, id,
507
+ specs.map do |s|
508
+ {
509
+ opcode: s.type,
510
+ path: s.path,
511
+ param: s.param,
512
+ xattr: s.xattr?,
513
+ expand_macros: s.expand_macros?,
514
+ create_path: s.create_path?,
515
+ }
516
+ end, options.to_backend
517
+ )
518
+ MutateInResult.new do |res|
519
+ res.transcoder = options.transcoder
520
+ res.cas = resp[:cas]
521
+ res.deleted = resp[:deleted]
522
+ res.mutation_token = extract_mutation_token(resp)
523
+ res.encoded = resp[:fields].map do |field|
524
+ SubDocumentField.new do |f|
525
+ f.index = field[:index]
526
+ f.path = field[:path]
527
+ f.value = field[:value]
528
+ end
529
+ end
530
+ end
531
+ end
532
+
533
+ private
534
+
535
+ def extract_mutation_token(resp)
536
+ return unless resp.key?(:mutation_token)
537
+
538
+ MutationToken.new do |token|
539
+ token.partition_id = resp[:mutation_token][:partition_id]
540
+ token.partition_uuid = resp[:mutation_token][:partition_uuid]
541
+ token.sequence_number = resp[:mutation_token][:sequence_number]
542
+ token.bucket_name = resp[:mutation_token][:bucket_name]
543
+ end
544
+ end
545
+
546
+ # @api private
547
+ # TODO: deprecate in 3.1
548
+ GetOptions = ::Couchbase::Options::Get
549
+ # @api private
550
+ # TODO: deprecate in 3.1
551
+ GetAndLockOptions = ::Couchbase::Options::GetAndLock
552
+ # @api private
553
+ # TODO: deprecate in 3.1
554
+ GetAndTouchOptions = ::Couchbase::Options::GetAndTouch
555
+ # @api private
556
+ # TODO: deprecate in 3.1
557
+ LookupInOptions = ::Couchbase::Options::LookupIn
558
+ # @api private
559
+ # TODO: deprecate in 3.1
560
+ MutateInOptions = ::Couchbase::Options::MutateIn
561
+ # @api private
562
+ # TODO: deprecate in 3.1
563
+ UnlockOptions = ::Couchbase::Options::Unlock
564
+ # @api private
565
+ # TODO: deprecate in 3.1
566
+ TouchOptions = ::Couchbase::Options::Touch
567
+ # @api private
568
+ # TODO: deprecate in 3.1
569
+ ReplaceOptions = ::Couchbase::Options::Replace
570
+ # @api private
571
+ # TODO: deprecate in 3.1
572
+ UpsertOptions = ::Couchbase::Options::Upsert
573
+ # @api private
574
+ # TODO: deprecate in 3.1
575
+ InsertOptions = ::Couchbase::Options::Insert
576
+ # @api private
577
+ # TODO: deprecate in 3.1
578
+ RemoveOptions = ::Couchbase::Options::Remove
579
+ # @api private
580
+ # TODO: deprecate in 3.1
581
+ ExistsOptions = ::Couchbase::Options::Exists
582
+ # @api private
583
+ # TODO: deprecate in 3.1
584
+ GetAnyReplicaOptions = ::Couchbase::Options::GetAnyReplica
585
+ # @api private
586
+ # TODO: deprecate in 3.1
587
+ GetAllReplicasOptions = ::Couchbase::Options::GetAllReplicas
588
+ end
589
+ end