couchbase 3.5.0-x86_64-darwin-20

Sign up to get free protection for your applications and to get access to all the features.
Files changed (126) 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.0/libcouchbase.bundle +0 -0
  7. data/lib/couchbase/3.1/libcouchbase.bundle +0 -0
  8. data/lib/couchbase/3.2/libcouchbase.bundle +0 -0
  9. data/lib/couchbase/3.3/libcouchbase.bundle +0 -0
  10. data/lib/couchbase/analytics_options.rb +107 -0
  11. data/lib/couchbase/authenticator.rb +64 -0
  12. data/lib/couchbase/binary_collection.rb +128 -0
  13. data/lib/couchbase/binary_collection_options.rb +24 -0
  14. data/lib/couchbase/bucket.rb +144 -0
  15. data/lib/couchbase/cluster.rb +460 -0
  16. data/lib/couchbase/cluster_registry.rb +49 -0
  17. data/lib/couchbase/collection.rb +705 -0
  18. data/lib/couchbase/collection_options.rb +399 -0
  19. data/lib/couchbase/config_profiles.rb +55 -0
  20. data/lib/couchbase/configuration.rb +56 -0
  21. data/lib/couchbase/datastructures/couchbase_list.rb +160 -0
  22. data/lib/couchbase/datastructures/couchbase_map.rb +194 -0
  23. data/lib/couchbase/datastructures/couchbase_queue.rb +134 -0
  24. data/lib/couchbase/datastructures/couchbase_set.rb +128 -0
  25. data/lib/couchbase/datastructures.rb +24 -0
  26. data/lib/couchbase/diagnostics.rb +181 -0
  27. data/lib/couchbase/errors.rb +376 -0
  28. data/lib/couchbase/json_transcoder.rb +39 -0
  29. data/lib/couchbase/key_value_scan.rb +117 -0
  30. data/lib/couchbase/libcouchbase.rb +6 -0
  31. data/lib/couchbase/logger.rb +85 -0
  32. data/lib/couchbase/management/analytics_index_manager.rb +1127 -0
  33. data/lib/couchbase/management/bucket_manager.rb +443 -0
  34. data/lib/couchbase/management/collection_manager.rb +470 -0
  35. data/lib/couchbase/management/collection_query_index_manager.rb +222 -0
  36. data/lib/couchbase/management/query_index_manager.rb +617 -0
  37. data/lib/couchbase/management/scope_search_index_manager.rb +198 -0
  38. data/lib/couchbase/management/search_index_manager.rb +424 -0
  39. data/lib/couchbase/management/user_manager.rb +468 -0
  40. data/lib/couchbase/management/view_index_manager.rb +237 -0
  41. data/lib/couchbase/management.rb +29 -0
  42. data/lib/couchbase/mutation_state.rb +63 -0
  43. data/lib/couchbase/options.rb +2837 -0
  44. data/lib/couchbase/protostellar/binary_collection.rb +55 -0
  45. data/lib/couchbase/protostellar/bucket.rb +51 -0
  46. data/lib/couchbase/protostellar/client.rb +99 -0
  47. data/lib/couchbase/protostellar/cluster.rb +163 -0
  48. data/lib/couchbase/protostellar/collection.rb +152 -0
  49. data/lib/couchbase/protostellar/connect_options.rb +63 -0
  50. data/lib/couchbase/protostellar/error_handling.rb +203 -0
  51. data/lib/couchbase/protostellar/generated/admin/bucket/v1/bucket_pb.rb +61 -0
  52. data/lib/couchbase/protostellar/generated/admin/bucket/v1/bucket_services_pb.rb +35 -0
  53. data/lib/couchbase/protostellar/generated/admin/collection/v1/collection_pb.rb +57 -0
  54. data/lib/couchbase/protostellar/generated/admin/collection/v1/collection_services_pb.rb +36 -0
  55. data/lib/couchbase/protostellar/generated/admin/query/v1/query_pb.rb +61 -0
  56. data/lib/couchbase/protostellar/generated/admin/query/v1/query_services_pb.rb +37 -0
  57. data/lib/couchbase/protostellar/generated/admin/search/v1/search_pb.rb +72 -0
  58. data/lib/couchbase/protostellar/generated/admin/search/v1/search_services_pb.rb +44 -0
  59. data/lib/couchbase/protostellar/generated/analytics/v1/analytics_pb.rb +52 -0
  60. data/lib/couchbase/protostellar/generated/analytics/v1/analytics_services_pb.rb +30 -0
  61. data/lib/couchbase/protostellar/generated/internal/hooks/v1/hooks_pb.rb +70 -0
  62. data/lib/couchbase/protostellar/generated/internal/hooks/v1/hooks_services_pb.rb +36 -0
  63. data/lib/couchbase/protostellar/generated/kv/v1/kv_pb.rb +97 -0
  64. data/lib/couchbase/protostellar/generated/kv/v1/kv_services_pb.rb +46 -0
  65. data/lib/couchbase/protostellar/generated/query/v1/query_pb.rb +57 -0
  66. data/lib/couchbase/protostellar/generated/query/v1/query_services_pb.rb +30 -0
  67. data/lib/couchbase/protostellar/generated/routing/v1/routing_pb.rb +52 -0
  68. data/lib/couchbase/protostellar/generated/routing/v1/routing_services_pb.rb +30 -0
  69. data/lib/couchbase/protostellar/generated/search/v1/search_pb.rb +99 -0
  70. data/lib/couchbase/protostellar/generated/search/v1/search_services_pb.rb +30 -0
  71. data/lib/couchbase/protostellar/generated/transactions/v1/transactions_pb.rb +57 -0
  72. data/lib/couchbase/protostellar/generated/transactions/v1/transactions_services_pb.rb +36 -0
  73. data/lib/couchbase/protostellar/generated/view/v1/view_pb.rb +51 -0
  74. data/lib/couchbase/protostellar/generated/view/v1/view_services_pb.rb +30 -0
  75. data/lib/couchbase/protostellar/generated.rb +9 -0
  76. data/lib/couchbase/protostellar/management/bucket_manager.rb +67 -0
  77. data/lib/couchbase/protostellar/management/collection_manager.rb +94 -0
  78. data/lib/couchbase/protostellar/management/collection_query_index_manager.rb +124 -0
  79. data/lib/couchbase/protostellar/management/query_index_manager.rb +112 -0
  80. data/lib/couchbase/protostellar/management.rb +24 -0
  81. data/lib/couchbase/protostellar/request.rb +78 -0
  82. data/lib/couchbase/protostellar/request_behaviour.rb +42 -0
  83. data/lib/couchbase/protostellar/request_generator/admin/bucket.rb +124 -0
  84. data/lib/couchbase/protostellar/request_generator/admin/collection.rb +94 -0
  85. data/lib/couchbase/protostellar/request_generator/admin/query.rb +130 -0
  86. data/lib/couchbase/protostellar/request_generator/admin.rb +24 -0
  87. data/lib/couchbase/protostellar/request_generator/kv.rb +474 -0
  88. data/lib/couchbase/protostellar/request_generator/query.rb +133 -0
  89. data/lib/couchbase/protostellar/request_generator/search.rb +387 -0
  90. data/lib/couchbase/protostellar/request_generator.rb +26 -0
  91. data/lib/couchbase/protostellar/response_converter/admin/bucket.rb +55 -0
  92. data/lib/couchbase/protostellar/response_converter/admin/collection.rb +42 -0
  93. data/lib/couchbase/protostellar/response_converter/admin/query.rb +59 -0
  94. data/lib/couchbase/protostellar/response_converter/admin.rb +24 -0
  95. data/lib/couchbase/protostellar/response_converter/kv.rb +151 -0
  96. data/lib/couchbase/protostellar/response_converter/query.rb +84 -0
  97. data/lib/couchbase/protostellar/response_converter/search.rb +136 -0
  98. data/lib/couchbase/protostellar/response_converter.rb +26 -0
  99. data/lib/couchbase/protostellar/retry/action.rb +38 -0
  100. data/lib/couchbase/protostellar/retry/orchestrator.rb +60 -0
  101. data/lib/couchbase/protostellar/retry/reason.rb +67 -0
  102. data/lib/couchbase/protostellar/retry/strategies/best_effort.rb +49 -0
  103. data/lib/couchbase/protostellar/retry/strategies.rb +26 -0
  104. data/lib/couchbase/protostellar/retry.rb +28 -0
  105. data/lib/couchbase/protostellar/scope.rb +57 -0
  106. data/lib/couchbase/protostellar/timeout_defaults.rb +30 -0
  107. data/lib/couchbase/protostellar/timeouts.rb +83 -0
  108. data/lib/couchbase/protostellar.rb +29 -0
  109. data/lib/couchbase/query_options.rb +120 -0
  110. data/lib/couchbase/railtie.rb +45 -0
  111. data/lib/couchbase/raw_binary_transcoder.rb +37 -0
  112. data/lib/couchbase/raw_json_transcoder.rb +38 -0
  113. data/lib/couchbase/raw_string_transcoder.rb +40 -0
  114. data/lib/couchbase/scope.rb +256 -0
  115. data/lib/couchbase/search_options.rb +1622 -0
  116. data/lib/couchbase/subdoc.rb +290 -0
  117. data/lib/couchbase/transcoder_flags.rb +62 -0
  118. data/lib/couchbase/utils/generic_logger_adapter.rb +38 -0
  119. data/lib/couchbase/utils/stdlib_logger_adapter.rb +65 -0
  120. data/lib/couchbase/utils/time.rb +69 -0
  121. data/lib/couchbase/utils.rb +21 -0
  122. data/lib/couchbase/version.rb +23 -0
  123. data/lib/couchbase/view_options.rb +65 -0
  124. data/lib/couchbase.rb +28 -0
  125. data/lib/rails/generators/couchbase/config/config_generator.rb +27 -0
  126. metadata +191 -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