couchbase 3.5.3-arm64-darwin

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