couchbase 3.4.0-arm64-darwin-20

Sign up to get free protection for your applications and to get access to all the features.
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