couchbase 3.0.1 → 3.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (140) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +73 -4
  3. data/ext/build_config.hxx.in +2 -0
  4. data/ext/build_version.hxx.in +11 -8
  5. data/ext/cmake/BuildTracing.cmake +1 -1
  6. data/ext/cmake/CompilerWarnings.cmake +5 -0
  7. data/ext/cmake/Testing.cmake +3 -6
  8. data/ext/couchbase/bucket.hxx +9 -1
  9. data/ext/couchbase/cbsasl/client.h +1 -1
  10. data/ext/couchbase/cluster.hxx +89 -6
  11. data/ext/couchbase/configuration.hxx +2 -0
  12. data/ext/couchbase/couchbase.cxx +1647 -507
  13. data/ext/couchbase/diagnostics.hxx +0 -3
  14. data/ext/couchbase/io/dns_client.hxx +2 -2
  15. data/ext/couchbase/io/http_command.hxx +6 -3
  16. data/ext/couchbase/io/http_session.hxx +14 -18
  17. data/ext/couchbase/io/http_session_manager.hxx +83 -2
  18. data/ext/couchbase/io/mcbp_command.hxx +4 -1
  19. data/ext/couchbase/io/mcbp_context.hxx +37 -0
  20. data/ext/couchbase/io/mcbp_session.hxx +91 -30
  21. data/ext/couchbase/operations.hxx +5 -0
  22. data/ext/couchbase/operations/analytics_dataset_create.hxx +3 -2
  23. data/ext/couchbase/operations/analytics_dataset_drop.hxx +3 -2
  24. data/ext/couchbase/operations/analytics_dataset_get_all.hxx +3 -2
  25. data/ext/couchbase/operations/analytics_dataverse_create.hxx +3 -2
  26. data/ext/couchbase/operations/analytics_dataverse_drop.hxx +3 -2
  27. data/ext/couchbase/operations/analytics_get_pending_mutations.hxx +3 -2
  28. data/ext/couchbase/operations/analytics_index_create.hxx +3 -2
  29. data/ext/couchbase/operations/analytics_index_drop.hxx +3 -2
  30. data/ext/couchbase/operations/analytics_index_get_all.hxx +5 -2
  31. data/ext/couchbase/operations/analytics_link_connect.hxx +3 -2
  32. data/ext/couchbase/operations/analytics_link_disconnect.hxx +3 -2
  33. data/ext/couchbase/operations/bucket_create.hxx +3 -2
  34. data/ext/couchbase/operations/bucket_drop.hxx +3 -2
  35. data/ext/couchbase/operations/bucket_flush.hxx +3 -2
  36. data/ext/couchbase/operations/bucket_get.hxx +3 -2
  37. data/ext/couchbase/operations/bucket_get_all.hxx +3 -2
  38. data/ext/couchbase/operations/bucket_update.hxx +3 -2
  39. data/ext/couchbase/operations/cluster_developer_preview_enable.hxx +3 -2
  40. data/ext/couchbase/operations/collection_create.hxx +3 -2
  41. data/ext/couchbase/operations/collection_drop.hxx +3 -2
  42. data/ext/couchbase/operations/collections_manifest_get.hxx +3 -2
  43. data/ext/couchbase/operations/document_analytics.hxx +3 -2
  44. data/ext/couchbase/operations/document_append.hxx +77 -0
  45. data/ext/couchbase/operations/document_decrement.hxx +3 -2
  46. data/ext/couchbase/operations/document_exists.hxx +3 -2
  47. data/ext/couchbase/operations/document_get.hxx +3 -2
  48. data/ext/couchbase/operations/document_get_and_lock.hxx +3 -2
  49. data/ext/couchbase/operations/document_get_and_touch.hxx +3 -2
  50. data/ext/couchbase/operations/document_get_projected.hxx +3 -2
  51. data/ext/couchbase/operations/document_increment.hxx +3 -2
  52. data/ext/couchbase/operations/document_insert.hxx +3 -2
  53. data/ext/couchbase/operations/document_lookup_in.hxx +8 -2
  54. data/ext/couchbase/operations/document_mutate_in.hxx +13 -2
  55. data/ext/couchbase/operations/document_prepend.hxx +77 -0
  56. data/ext/couchbase/operations/document_query.hxx +3 -2
  57. data/ext/couchbase/operations/document_remove.hxx +5 -2
  58. data/ext/couchbase/operations/document_replace.hxx +3 -2
  59. data/ext/couchbase/operations/document_search.hxx +3 -2
  60. data/ext/couchbase/operations/document_touch.hxx +3 -2
  61. data/ext/couchbase/operations/document_unlock.hxx +3 -2
  62. data/ext/couchbase/operations/document_upsert.hxx +3 -2
  63. data/ext/couchbase/operations/document_view.hxx +3 -2
  64. data/ext/couchbase/operations/group_drop.hxx +3 -2
  65. data/ext/couchbase/operations/group_get.hxx +3 -2
  66. data/ext/couchbase/operations/group_get_all.hxx +3 -2
  67. data/ext/couchbase/operations/group_upsert.hxx +3 -2
  68. data/ext/couchbase/operations/http_noop.hxx +78 -0
  69. data/ext/couchbase/operations/mcbp_noop.hxx +61 -0
  70. data/ext/couchbase/operations/query_index_build_deferred.hxx +3 -2
  71. data/ext/couchbase/operations/query_index_create.hxx +3 -2
  72. data/ext/couchbase/operations/query_index_drop.hxx +3 -2
  73. data/ext/couchbase/operations/query_index_get_all.hxx +3 -2
  74. data/ext/couchbase/operations/role_get_all.hxx +3 -2
  75. data/ext/couchbase/operations/scope_create.hxx +3 -2
  76. data/ext/couchbase/operations/scope_drop.hxx +3 -2
  77. data/ext/couchbase/operations/scope_get_all.hxx +3 -2
  78. data/ext/couchbase/operations/search_get_stats.hxx +3 -2
  79. data/ext/couchbase/operations/search_index_analyze_document.hxx +3 -2
  80. data/ext/couchbase/operations/search_index_control_ingest.hxx +3 -2
  81. data/ext/couchbase/operations/search_index_control_plan_freeze.hxx +3 -2
  82. data/ext/couchbase/operations/search_index_control_query.hxx +3 -2
  83. data/ext/couchbase/operations/search_index_drop.hxx +3 -2
  84. data/ext/couchbase/operations/search_index_get.hxx +3 -2
  85. data/ext/couchbase/operations/search_index_get_all.hxx +3 -2
  86. data/ext/couchbase/operations/search_index_get_documents_count.hxx +3 -2
  87. data/ext/couchbase/operations/search_index_get_stats.hxx +3 -2
  88. data/ext/couchbase/operations/search_index_upsert.hxx +3 -2
  89. data/ext/couchbase/operations/user_drop.hxx +3 -2
  90. data/ext/couchbase/operations/user_get.hxx +3 -2
  91. data/ext/couchbase/operations/user_get_all.hxx +3 -2
  92. data/ext/couchbase/operations/user_upsert.hxx +3 -2
  93. data/ext/couchbase/operations/view_index_drop.hxx +3 -2
  94. data/ext/couchbase/operations/view_index_get.hxx +3 -2
  95. data/ext/couchbase/operations/view_index_get_all.hxx +3 -2
  96. data/ext/couchbase/operations/view_index_upsert.hxx +3 -2
  97. data/ext/couchbase/platform/terminate_handler.cc +5 -2
  98. data/ext/couchbase/protocol/client_opcode.hxx +368 -0
  99. data/ext/couchbase/protocol/cmd_append.hxx +145 -0
  100. data/ext/couchbase/protocol/cmd_hello.hxx +1 -0
  101. data/ext/couchbase/protocol/cmd_lookup_in.hxx +11 -3
  102. data/ext/couchbase/protocol/cmd_mutate_in.hxx +46 -4
  103. data/ext/couchbase/protocol/cmd_noop.hxx +82 -0
  104. data/ext/couchbase/protocol/cmd_prepend.hxx +145 -0
  105. data/ext/couchbase/protocol/durability_level.hxx +16 -0
  106. data/ext/couchbase/protocol/hello_feature.hxx +9 -0
  107. data/ext/couchbase/protocol/unsigned_leb128.h +2 -2
  108. data/ext/couchbase/service_type.hxx +1 -1
  109. data/ext/couchbase/version.hxx +18 -4
  110. data/ext/extconf.rb +9 -6
  111. data/ext/test/CMakeLists.txt +5 -0
  112. data/ext/test/test_helper.hxx +3 -3
  113. data/ext/test/test_helper_native.hxx +2 -5
  114. data/ext/test/test_native_binary_operations.cxx +186 -0
  115. data/ext/test/test_native_diagnostics.cxx +54 -3
  116. data/ext/test/test_ruby_trivial_crud.cxx +1 -1
  117. data/lib/couchbase.rb +1 -0
  118. data/lib/couchbase/analytics_options.rb +1 -71
  119. data/lib/couchbase/binary_collection.rb +60 -22
  120. data/lib/couchbase/binary_collection_options.rb +0 -76
  121. data/lib/couchbase/bucket.rb +40 -36
  122. data/lib/couchbase/cluster.rb +89 -156
  123. data/lib/couchbase/collection.rb +290 -72
  124. data/lib/couchbase/collection_options.rb +30 -243
  125. data/lib/couchbase/datastructures/couchbase_list.rb +5 -16
  126. data/lib/couchbase/datastructures/couchbase_map.rb +5 -16
  127. data/lib/couchbase/datastructures/couchbase_queue.rb +5 -16
  128. data/lib/couchbase/datastructures/couchbase_set.rb +5 -16
  129. data/lib/couchbase/diagnostics.rb +181 -0
  130. data/lib/couchbase/json_transcoder.rb +1 -1
  131. data/lib/couchbase/{common_options.rb → logger.rb} +24 -11
  132. data/lib/couchbase/management/query_index_manager.rb +1 -1
  133. data/lib/couchbase/management/user_manager.rb +3 -0
  134. data/lib/couchbase/options.rb +2094 -0
  135. data/lib/couchbase/query_options.rb +1 -144
  136. data/lib/couchbase/scope.rb +8 -25
  137. data/lib/couchbase/search_options.rb +0 -93
  138. data/lib/couchbase/version.rb +20 -1
  139. data/lib/couchbase/view_options.rb +1 -91
  140. metadata +19 -7
@@ -45,18 +45,34 @@ module Couchbase
45
45
  # Fetches the full document from the collection
46
46
  #
47
47
  # @param [String] id the document id which is used to uniquely identify it
48
- # @param [GetOptions] options request customization
48
+ # @param [Options::Get] options request customization
49
+ #
50
+ # @example Get document contents
51
+ # res = collection.get("customer123")
52
+ # res.content["addresses"]
53
+ #
54
+ # # {"billing"=>
55
+ # # {"line1"=>"123 Any Street", "line2"=>"Anytown", "country"=>"United Kingdom"},
56
+ # # "delivery"=>
57
+ # # {"line1"=>"123 Any Street", "line2"=>"Anytown", "country"=>"United Kingdom"}}
58
+ #
59
+ # @example Get partial document using projections
60
+ # res = collection.get("customer123", Options::Get(projections: ["name", "addresses.billing"]))
61
+ # res.content
62
+ #
63
+ # # {"addresses"=>
64
+ # # {"billing"=>
65
+ # # {"country"=>"United Kingdom",
66
+ # # "line1"=>"123 Any Street",
67
+ # # "line2"=>"Anytown"}},
68
+ # # "name"=>"Douglas Reynholm"}
49
69
  #
50
70
  # @return [GetResult]
51
- def get(id, options = GetOptions.new)
71
+ def get(id, options = Options::Get.new)
52
72
  resp = if options.need_projected_get?
53
- @backend.document_get_projected(bucket_name, "#{@scope_name}.#{@name}", id,
54
- options.timeout,
55
- options.with_expiry,
56
- options.projections,
57
- options.preserve_array_indexes)
73
+ @backend.document_get_projected(bucket_name, "#{@scope_name}.#{@name}", id, options.to_backend)
58
74
  else
59
- @backend.document_get(bucket_name, "#{@scope_name}.#{@name}", id, options.timeout)
75
+ @backend.document_get(bucket_name, "#{@scope_name}.#{@name}", id, options.to_backend)
60
76
  end
61
77
  GetResult.new do |res|
62
78
  res.transcoder = options.transcoder
@@ -67,15 +83,54 @@ module Couchbase
67
83
  end
68
84
  end
69
85
 
86
+ # Fetches multiple documents from the collection.
87
+ #
88
+ # @note that it will not generate {Error::DocumentNotFound} exceptions in this case. The caller should check
89
+ # {GetResult#error} property of the result
90
+ #
91
+ # @param [Array<String>] ids the array of document identifiers
92
+ # @param [Options::GetMulti] options request customization
93
+ #
94
+ # @example Fetch "foo" and "bar" in a batch
95
+ # res = collection.get(["foo", "bar"], Options::GetMulti(timeout: 3_000))
96
+ # res[0].content #=> content of "foo"
97
+ # res[1].content #=> content of "bar"
98
+ #
99
+ # @return [Array<GetResult>]
100
+ def get_multi(ids, options = Options::GetMulti.new)
101
+ collection_spec = "#{@scope_name}.#{@name}"
102
+ resp = @backend.document_get_multi(ids.map { |id| [bucket_name, collection_spec, id] }, options.to_backend)
103
+ resp.map do |entry|
104
+ GetResult.new do |res|
105
+ res.transcoder = options.transcoder
106
+ res.cas = entry[:cas]
107
+ res.flags = entry[:flags]
108
+ res.encoded = entry[:content]
109
+ res.error = entry[:error]
110
+ end
111
+ end
112
+ end
113
+
70
114
  # Fetches the full document and write-locks it for the given duration
71
115
  #
72
116
  # @param [String] id the document id which is used to uniquely identify it.
73
- # @param [Integer] lock_time how long to lock the document (values over 30 seconds will be capped)
74
- # @param [GetAndLockOptions] options request customization
117
+ # @param [Integer, #in_seconds] lock_time how long to lock the document (values over 30 seconds will be capped)
118
+ # @param [Options::GetAndLock] options request customization
119
+ #
120
+ # @example Retrieve document and lock for 10 seconds
121
+ # collection.get_and_lock("customer123", 10, Options::GetAndLock(timeout: 3_000))
122
+ #
123
+ # @example Update document pessimistically
124
+ # res = collection.get_and_lock("customer123", 10)
125
+ # user_data = res.content
126
+ # user_data["admin"] = true
127
+ # collection.replace("user", user_data, Options::Upsert(cas: res.cas))
75
128
  #
76
129
  # @return [GetResult]
77
- def get_and_lock(id, lock_time, options = GetAndLockOptions.new)
78
- resp = @backend.document_get_and_lock(bucket_name, "#{@scope_name}.#{@name}", id, options.timeout, lock_time)
130
+ def get_and_lock(id, lock_time, options = Options::GetAndLock.new)
131
+ resp = @backend.document_get_and_lock(bucket_name, "#{@scope_name}.#{@name}", id,
132
+ lock_time.respond_to?(:in_seconds) ? lock_time.public_send(:in_seconds) : lock_time,
133
+ options.to_backend)
79
134
  GetResult.new do |res|
80
135
  res.transcoder = options.transcoder
81
136
  res.cas = resp[:cas]
@@ -87,12 +142,17 @@ module Couchbase
87
142
  # Fetches a full document and resets its expiration time to the duration provided
88
143
  #
89
144
  # @param [String] id the document id which is used to uniquely identify it.
90
- # @param [Integer] expiry the new expiration time for the document
91
- # @param [GetAndTouchOptions] options request customization
145
+ # @param [Integer, #in_seconds] expiry the new expiration time for the document
146
+ # @param [Options::GetAndTouch] options request customization
147
+ #
148
+ # @example Retrieve document and prolong its expiration for another 10 seconds
149
+ # collection.get_and_touch("customer123", 10)
92
150
  #
93
151
  # @return [GetResult]
94
- def get_and_touch(id, expiry, options = GetAndTouchOptions.new)
95
- resp = @backend.document_get_and_touch(bucket_name, "#{@scope_name}.#{@name}", id, options.timeout, expiry)
152
+ def get_and_touch(id, expiry, options = Options::GetAndTouch.new)
153
+ resp = @backend.document_get_and_touch(bucket_name, "#{@scope_name}.#{@name}", id,
154
+ expiry.respond_to?(:in_seconds) ? expiry.public_send(:in_seconds) : expiry,
155
+ options.to_backend)
96
156
  GetResult.new do |res|
97
157
  res.transcoder = options.transcoder
98
158
  res.cas = resp[:cas]
@@ -104,27 +164,31 @@ module Couchbase
104
164
  # Reads from all available replicas and the active node and returns the results
105
165
  #
106
166
  # @param [String] id the document id which is used to uniquely identify it.
107
- # @param [GetAllReplicasOptions] options request customization
167
+ # @param [Options::GetAllReplicas] options request customization
108
168
  #
109
169
  # @return [Array<GetReplicaResult>]
110
- def get_all_replicas(id, options = GetAllReplicasOptions.new) end
170
+ def get_all_replicas(id, options = Options::GetAllReplicas.new) end
111
171
 
112
172
  # Reads all available replicas, and returns the first found
113
173
  #
114
174
  # @param [String] id the document id which is used to uniquely identify it.
115
- # @param [GetAnyReplicaOptions] options request customization
175
+ # @param [Options::GetAnyReplica] options request customization
116
176
  #
117
177
  # @return [GetReplicaResult]
118
- def get_any_replica(id, options = GetAnyReplicaOptions.new) end
178
+ def get_any_replica(id, options = Options::GetAnyReplica.new) end
119
179
 
120
180
  # Checks if the given document ID exists on the active partition.
121
181
  #
122
182
  # @param [String] id the document id which is used to uniquely identify it.
123
- # @param [ExistsOptions] options request customization
183
+ # @param [Options::Exists] options request customization
184
+ #
185
+ # @example Check if the document exists without fetching its contents
186
+ # res = collection.exists("customer123")
187
+ # res.exists? #=> true
124
188
  #
125
189
  # @return [ExistsResult]
126
- def exists(id, options = ExistsOptions.new)
127
- resp = @backend.document_exists(bucket_name, "#{@scope_name}.#{@name}", id, options.timeout)
190
+ def exists(id, options = Options::Exists.new)
191
+ resp = @backend.document_exists(bucket_name, "#{@scope_name}.#{@name}", id, options.to_backend)
128
192
  ExistsResult.new do |res|
129
193
  res.status = resp[:status]
130
194
  res.partition_id = resp[:partition_id]
@@ -135,32 +199,91 @@ module Couchbase
135
199
  # Removes a document from the collection
136
200
  #
137
201
  # @param [String] id the document id which is used to uniquely identify it.
138
- # @param [RemoveOptions] options request customization
202
+ # @param [Options::Remove] options request customization
203
+ #
204
+ # @example Remove the document in collection
205
+ # res = collection.remove("customer123")
206
+ # res.cas #=> 241994216651798
207
+ #
208
+ # @example Remove the document in collection, but apply optimistic lock
209
+ # res = collection.upsert("mydoc", {"foo" => 42})
210
+ # res.cas #=> 7751414725654
211
+ #
212
+ # begin
213
+ # res = collection.remove("mydoc", Options::Remove(cas: 3735928559))
214
+ # rescue Error::CasMismatch
215
+ # puts "Failed to remove the document, it might be changed by other application"
216
+ # end
139
217
  #
140
218
  # @return [MutationResult]
141
- def remove(id, options = RemoveOptions.new)
142
- resp = @backend.document_remove(bucket_name, "#{@scope_name}.#{@name}", id, options.timeout, {
143
- durability_level: options.durability_level,
144
- })
219
+ def remove(id, options = Options::Remove.new)
220
+ resp = @backend.document_remove(bucket_name, "#{@scope_name}.#{@name}", id, options.to_backend)
145
221
  MutationResult.new do |res|
146
222
  res.cas = resp[:cas]
147
223
  res.mutation_token = extract_mutation_token(resp)
148
224
  end
149
225
  end
150
226
 
227
+ # Removes a list of the documents from the collection
228
+ #
229
+ # @note that it will not generate {Error::DocumentNotFound} or {Error::CasMismatch} exceptions in this case.
230
+ # The caller should check {MutationResult#error} property of the result
231
+ #
232
+ # @param [Array<String, Array>] ids the array of document ids, or ID/CAS pairs +[String,Integer]+
233
+ # @param [Options::RemoveMulti] options request customization
234
+ #
235
+ # @example Remove two documents in collection. For "mydoc" apply optimistic lock
236
+ # res = collection.upsert("mydoc", {"foo" => 42})
237
+ # res.cas #=> 7751414725654
238
+ #
239
+ # res = collection.remove_multi(["foo", ["mydoc", res.cas]])
240
+ # if res[1].error.is_a?(Error::CasMismatch)
241
+ # puts "Failed to remove the document, it might be changed by other application"
242
+ # end
243
+ #
244
+ # @return [Array<MutationResult>]
245
+ def remove_multi(ids, options = Options::RemoveMulti.new)
246
+ collection_spec = "#{@scope_name}.#{@name}"
247
+ resp = @backend.document_remove_multi(ids.map do |id|
248
+ case id
249
+ when String
250
+ [bucket_name, collection_spec, id, nil]
251
+ when Array
252
+ [bucket_name, collection_spec, id[0], id[1]]
253
+ else
254
+ raise ArgumentError, "id argument of remove_multi must be a String or Array<String, Integer>, given: #{id.inspect}"
255
+ end
256
+ end, options.to_backend)
257
+ resp.map do |entry|
258
+ MutationResult.new do |res|
259
+ res.cas = entry[:cas]
260
+ res.mutation_token = extract_mutation_token(entry)
261
+ end
262
+ end
263
+ end
264
+
151
265
  # Inserts a full document which does not exist yet
152
266
  #
153
267
  # @param [String] id the document id which is used to uniquely identify it.
154
268
  # @param [Object] content the document content to insert
155
- # @param [InsertOptions] options request customization
269
+ # @param [Options::Insert] options request customization
270
+ #
271
+ # @example Insert new document in collection
272
+ # res = collection.insert("mydoc", {"foo" => 42}, Options::Insert(expiry: 20))
273
+ # res.cas #=> 242287264414742
274
+ #
275
+ # @example Handle error when the document already exists
276
+ # collection.exists("mydoc").exists? #=> true
277
+ # begin
278
+ # res = collection.insert("mydoc", {"foo" => 42})
279
+ # rescue Error::DocumentExists
280
+ # puts "Failed to insert the document, it already exists in the collection"
281
+ # end
156
282
  #
157
283
  # @return [MutationResult]
158
- def insert(id, content, options = InsertOptions.new)
159
- blob, flags = options.transcoder.encode(content)
160
- resp = @backend.document_insert(bucket_name, "#{@scope_name}.#{@name}", id, options.timeout, blob, flags, {
161
- durability_level: options.durability_level,
162
- expiry: options.expiry,
163
- })
284
+ def insert(id, content, options = Options::Insert.new)
285
+ blob, flags = options.transcoder ? options.transcoder.encode(content) : [content, 0]
286
+ resp = @backend.document_insert(bucket_name, "#{@scope_name}.#{@name}", id, blob, flags, options.to_backend)
164
287
  MutationResult.new do |res|
165
288
  res.cas = resp[:cas]
166
289
  res.mutation_token = extract_mutation_token(resp)
@@ -171,35 +294,69 @@ module Couchbase
171
294
  #
172
295
  # @param [String] id the document id which is used to uniquely identify it.
173
296
  # @param [Object] content the document content to upsert
174
- # @param [UpsertOptions] options request customization
297
+ # @param [Options::Upsert] options request customization
298
+ #
299
+ # @example Upsert new document in collection
300
+ # res = collection.upsert("mydoc", {"foo" => 42}, Options::Upsert(expiry: 20))
301
+ # res.cas #=> 242287264414742
175
302
  #
176
303
  # @return [MutationResult]
177
- def upsert(id, content, options = UpsertOptions.new)
178
- blob, flags = options.transcoder.encode(content)
179
- resp = @backend.document_upsert(bucket_name, "#{@scope_name}.#{@name}", id, options.timeout, blob, flags, {
180
- durability_level: options.durability_level,
181
- expiry: options.expiry,
182
- })
304
+ def upsert(id, content, options = Options::Upsert.new)
305
+ blob, flags = options.transcoder ? options.transcoder.encode(content) : [content, 0]
306
+ resp = @backend.document_upsert(bucket_name, "#{@scope_name}.#{@name}", id, blob, flags, options.to_backend)
183
307
  MutationResult.new do |res|
184
308
  res.cas = resp[:cas]
185
309
  res.mutation_token = extract_mutation_token(resp)
186
310
  end
187
311
  end
188
312
 
313
+ # Upserts (inserts or updates) a list of documents which might or might not exist yet
314
+ #
315
+ # @note that it will not generate exceptions in this case. The caller should check {MutationResult#error} property of the
316
+ # result
317
+ #
318
+ # @param [Array<Array>] id_content array of tuples +String,Object+, where first entry treated as document key,
319
+ # and the second as value to upsert.
320
+ # @param [Options::UpsertMulti] options request customization
321
+ #
322
+ # @example Upsert two documents with IDs "foo" and "bar" into a collection
323
+ # res = collection.upsert_multi([
324
+ # "foo", {"foo" => 42},
325
+ # "bar", {"bar" => "some value"}
326
+ # ])
327
+ # res[0].cas #=> 7751414725654
328
+ # res[1].cas #=> 7751418925851
329
+ #
330
+ # @return [Array<MutationResult>]
331
+ def upsert_multi(id_content, options = Options::UpsertMulti.new)
332
+ collection_spec = "#{@scope_name}.#{@name}"
333
+ resp = @backend.document_upsert_multi(id_content.map do |(id, content)|
334
+ blob, flags = options.transcoder ? options.transcoder.encode(content) : [content, 0]
335
+ [bucket_name, collection_spec, id, blob, flags]
336
+ end, options.to_backend)
337
+ resp.map do |entry|
338
+ MutationResult.new do |res|
339
+ res.cas = entry[:cas]
340
+ res.mutation_token = extract_mutation_token(entry)
341
+ end
342
+ end
343
+ end
344
+
189
345
  # Replaces a full document which already exists
190
346
  #
191
347
  # @param [String] id the document id which is used to uniquely identify it.
192
348
  # @param [Object] content the document content to upsert
193
- # @param [ReplaceOptions] options request customization
349
+ # @param [Options::Replace] options request customization
350
+ #
351
+ # @example Replace new document in collection with optimistic locking
352
+ # res = collection.get("mydoc")
353
+ # res = collection.replace("mydoc", {"foo" => 42}, Options::Replace(cas: res.cas))
354
+ # res.cas #=> 242287264414742
194
355
  #
195
356
  # @return [MutationResult]
196
- def replace(id, content, options = ReplaceOptions.new)
197
- blob, flags = options.transcoder.encode(content)
198
- resp = @backend.document_replace(bucket_name, "#{@scope_name}.#{@name}", id, options.timeout, blob, flags, {
199
- durability_level: options.durability_level,
200
- expiry: options.expiry,
201
- cas: options.cas,
202
- })
357
+ def replace(id, content, options = Options::Replace.new)
358
+ blob, flags = options.transcoder ? options.transcoder.encode(content) : [content, 0]
359
+ resp = @backend.document_replace(bucket_name, "#{@scope_name}.#{@name}", id, blob, flags, options.to_backend)
203
360
  MutationResult.new do |res|
204
361
  res.cas = resp[:cas]
205
362
  res.mutation_token = extract_mutation_token(resp)
@@ -209,12 +366,17 @@ module Couchbase
209
366
  # Update the expiration of the document with the given id
210
367
  #
211
368
  # @param [String] id the document id which is used to uniquely identify it.
212
- # @param [Integer] expiry new expiration time for the document
213
- # @param [TouchOptions] options request customization
369
+ # @param [Integer, #in_seconds] expiry new expiration time for the document
370
+ # @param [Options::Touch] options request customization
371
+ #
372
+ # @example Reset expiration timer for document to 30 seconds
373
+ # res = collection.touch("customer123", 30)
214
374
  #
215
375
  # @return [MutationResult]
216
- def touch(id, expiry, options = TouchOptions.new)
217
- resp = @backend.document_touch(bucket_name, "#{@scope_name}.#{@name}", id, options.timeout, expiry)
376
+ def touch(id, expiry, options = Options::Touch.new)
377
+ resp = @backend.document_touch(bucket_name, "#{@scope_name}.#{@name}", id,
378
+ expiry.respond_to?(:in_seconds) ? expiry.public_send(:in_seconds) : expiry,
379
+ options.to_backend)
218
380
  MutationResult.new do |res|
219
381
  res.cas = resp[:cas]
220
382
  end
@@ -224,34 +386,47 @@ module Couchbase
224
386
  #
225
387
  # @param [String] id the document id which is used to uniquely identify it.
226
388
  # @param [Integer] cas CAS value which is needed to unlock the document
227
- # @param [UnlockOptions] options request customization
389
+ # @param [Options::Unlock] options request customization
390
+ #
391
+ # @example Lock (pessimistically) and unlock document
392
+ # res = collection.get_and_lock("customer123", 10)
393
+ # collection.unlock("customer123", res.cas)
394
+ #
395
+ # @return [void]
228
396
  #
229
397
  # @raise [Error::DocumentNotFound]
230
- def unlock(id, cas, options = UnlockOptions.new)
231
- @backend.document_unlock(bucket_name, "#{@scope_name}.#{@name}", id, options.timeout, cas)
398
+ def unlock(id, cas, options = Options::Unlock.new)
399
+ @backend.document_unlock(bucket_name, "#{@scope_name}.#{@name}", id, cas, options.to_backend)
232
400
  end
233
401
 
234
402
  # Performs lookups to document fragments
235
403
  #
236
404
  # @param [String] id the document id which is used to uniquely identify it.
237
405
  # @param [Array<LookupInSpec>] specs the list of specifications which describe the types of the lookups to perform
238
- # @param [LookupInOptions] options request customization
406
+ # @param [Options::LookupIn] options request customization
407
+ #
408
+ # @example Get list of IDs of completed purchases
409
+ # lookup_specs = [
410
+ # LookupInSpec::get("purchases.complete")
411
+ # ]
412
+ # collection.lookup_in("customer123", lookup_specs)
239
413
  #
240
414
  # @return [LookupInResult]
241
- def lookup_in(id, specs, options = LookupInOptions.new)
415
+ def lookup_in(id, specs, options = Options::LookupIn.new)
242
416
  resp = @backend.document_lookup_in(
243
- bucket_name, "#{@scope_name}.#{@name}", id, options.timeout, options.access_deleted,
417
+ bucket_name, "#{@scope_name}.#{@name}", id,
244
418
  specs.map do |s|
245
419
  {
246
420
  opcode: s.type,
247
421
  xattr: s.xattr?,
248
422
  path: s.path,
249
423
  }
250
- end
424
+ end, options.to_backend
251
425
  )
252
426
  LookupInResult.new do |res|
253
427
  res.transcoder = options.transcoder
254
428
  res.cas = resp[:cas]
429
+ res.deleted = resp[:deleted]
255
430
  res.encoded = resp[:fields].map do |field|
256
431
  SubDocumentField.new do |f|
257
432
  f.exists = field[:exists]
@@ -270,12 +445,18 @@ module Couchbase
270
445
  #
271
446
  # @param [String] id the document id which is used to uniquely identify it.
272
447
  # @param [Array<MutateInSpec>] specs the list of specifications which describe the types of the lookups to perform
273
- # @param [MutateInOptions] options request customization
448
+ # @param [Options::MutateIn] options request customization
449
+ #
450
+ # @example Append number into subarray of the document
451
+ # mutation_specs = [
452
+ # MutateInSpec::array_append("purchases.complete", [42])
453
+ # ]
454
+ # collection.mutate_in("customer123", mutation_specs, Options::MutateIn(expiry: 10))
274
455
  #
275
456
  # @return [MutateInResult]
276
- def mutate_in(id, specs, options = MutateInOptions.new)
457
+ def mutate_in(id, specs, options = Options::MutateIn.new)
277
458
  resp = @backend.document_mutate_in(
278
- bucket_name, "#{@scope_name}.#{@name}", id, options.timeout,
459
+ bucket_name, "#{@scope_name}.#{@name}", id,
279
460
  specs.map do |s|
280
461
  {
281
462
  opcode: s.type,
@@ -285,18 +466,12 @@ module Couchbase
285
466
  expand_macros: s.expand_macros?,
286
467
  create_path: s.create_path?,
287
468
  }
288
- end,
289
- {
290
- durability_level: options.durability_level,
291
- store_semantics: options.store_semantics,
292
- access_deleted: options.access_deleted,
293
- cas: options.cas,
294
- expiry: options.expiry,
295
- }
469
+ end, options.to_backend
296
470
  )
297
471
  result = MutateInResult.new do |res|
298
472
  res.transcoder = options.transcoder
299
473
  res.cas = resp[:cas]
474
+ res.deleted = resp[:deleted]
300
475
  res.mutation_token = extract_mutation_token(resp)
301
476
  res.first_error_index = resp[:first_error_index]
302
477
  res.encoded = resp[:fields].map do |field|
@@ -325,5 +500,48 @@ module Couchbase
325
500
  token.bucket_name = resp[:mutation_token][:bucket_name]
326
501
  end
327
502
  end
503
+
504
+ # @api private
505
+ # TODO: deprecate in 3.1
506
+ GetOptions = ::Couchbase::Options::Get
507
+ # @api private
508
+ # TODO: deprecate in 3.1
509
+ GetAndLockOptions = ::Couchbase::Options::GetAndLock
510
+ # @api private
511
+ # TODO: deprecate in 3.1
512
+ GetAndTouchOptions = ::Couchbase::Options::GetAndTouch
513
+ # @api private
514
+ # TODO: deprecate in 3.1
515
+ LookupInOptions = ::Couchbase::Options::LookupIn
516
+ # @api private
517
+ # TODO: deprecate in 3.1
518
+ MutateInOptions = ::Couchbase::Options::MutateIn
519
+ # @api private
520
+ # TODO: deprecate in 3.1
521
+ UnlockOptions = ::Couchbase::Options::Unlock
522
+ # @api private
523
+ # TODO: deprecate in 3.1
524
+ TouchOptions = ::Couchbase::Options::Touch
525
+ # @api private
526
+ # TODO: deprecate in 3.1
527
+ ReplaceOptions = ::Couchbase::Options::Replace
528
+ # @api private
529
+ # TODO: deprecate in 3.1
530
+ UpsertOptions = ::Couchbase::Options::Upsert
531
+ # @api private
532
+ # TODO: deprecate in 3.1
533
+ InsertOptions = ::Couchbase::Options::Insert
534
+ # @api private
535
+ # TODO: deprecate in 3.1
536
+ RemoveOptions = ::Couchbase::Options::Remove
537
+ # @api private
538
+ # TODO: deprecate in 3.1
539
+ ExistsOptions = ::Couchbase::Options::Exists
540
+ # @api private
541
+ # TODO: deprecate in 3.1
542
+ GetAnyReplicaOptions = ::Couchbase::Options::GetAnyReplica
543
+ # @api private
544
+ # TODO: deprecate in 3.1
545
+ GetAllReplicasOptions = ::Couchbase::Options::GetAllReplicas
328
546
  end
329
547
  end