mongo 2.14.1 → 2.15.0.alpha

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 (230) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/README.md +4 -1
  4. data/Rakefile +8 -15
  5. data/lib/mongo/auth/aws/conversation.rb +1 -4
  6. data/lib/mongo/auth/base.rb +13 -7
  7. data/lib/mongo/auth/conversation_base.rb +32 -0
  8. data/lib/mongo/auth/cr/conversation.rb +6 -29
  9. data/lib/mongo/auth/gssapi/conversation.rb +4 -15
  10. data/lib/mongo/auth/ldap/conversation.rb +3 -14
  11. data/lib/mongo/auth/sasl_conversation_base.rb +1 -13
  12. data/lib/mongo/auth/scram_conversation_base.rb +7 -34
  13. data/lib/mongo/auth/user/view.rb +16 -9
  14. data/lib/mongo/auth/x509/conversation.rb +4 -25
  15. data/lib/mongo/bulk_write.rb +21 -18
  16. data/lib/mongo/client.rb +82 -6
  17. data/lib/mongo/cluster/reapers/cursor_reaper.rb +6 -2
  18. data/lib/mongo/cluster.rb +19 -2
  19. data/lib/mongo/collection/view/aggregation.rb +1 -1
  20. data/lib/mongo/collection/view/change_stream.rb +1 -1
  21. data/lib/mongo/collection/view/iterable.rb +7 -17
  22. data/lib/mongo/collection/view/map_reduce.rb +2 -2
  23. data/lib/mongo/collection/view/readable.rb +42 -20
  24. data/lib/mongo/collection/view/writable.rb +14 -14
  25. data/lib/mongo/collection.rb +6 -6
  26. data/lib/mongo/cursor.rb +2 -12
  27. data/lib/mongo/database/view.rb +1 -1
  28. data/lib/mongo/database.rb +8 -3
  29. data/lib/mongo/error/bulk_write_error.rb +17 -3
  30. data/lib/mongo/error/internal_driver_error.rb +22 -0
  31. data/lib/mongo/error/operation_failure.rb +21 -2
  32. data/lib/mongo/error/parser.rb +65 -12
  33. data/lib/mongo/error/server_api_conflict.rb +23 -0
  34. data/lib/mongo/error/server_api_not_supported.rb +24 -0
  35. data/lib/mongo/error/unmet_dependency.rb +21 -0
  36. data/lib/mongo/error.rb +9 -1
  37. data/lib/mongo/index/view.rb +21 -11
  38. data/lib/mongo/monitoring/event/server_heartbeat_failed.rb +27 -16
  39. data/lib/mongo/monitoring/event/server_heartbeat_succeeded.rb +26 -15
  40. data/lib/mongo/monitoring.rb +13 -4
  41. data/lib/mongo/operation/collections_info/command.rb +2 -2
  42. data/lib/mongo/operation/collections_info.rb +18 -1
  43. data/lib/mongo/operation/context.rb +99 -0
  44. data/lib/mongo/operation/indexes.rb +15 -1
  45. data/lib/mongo/operation/insert/command.rb +2 -2
  46. data/lib/mongo/operation/insert/legacy.rb +2 -2
  47. data/lib/mongo/operation/insert/op_msg.rb +2 -2
  48. data/lib/mongo/operation/list_collections/result.rb +4 -1
  49. data/lib/mongo/operation/parallel_scan/command.rb +2 -1
  50. data/lib/mongo/operation/result.rb +2 -0
  51. data/lib/mongo/operation/shared/executable.rb +24 -14
  52. data/lib/mongo/operation/shared/executable_no_validate.rb +2 -2
  53. data/lib/mongo/operation/shared/op_msg_or_command.rb +1 -7
  54. data/lib/mongo/operation/shared/op_msg_or_find_command.rb +1 -7
  55. data/lib/mongo/operation/shared/polymorphic_operation.rb +39 -0
  56. data/lib/mongo/operation/shared/read_preference_supported.rb +36 -38
  57. data/lib/mongo/operation/shared/response_handling.rb +23 -23
  58. data/lib/mongo/operation/shared/sessions_supported.rb +15 -5
  59. data/lib/mongo/operation/shared/write.rb +8 -18
  60. data/lib/mongo/operation.rb +2 -2
  61. data/lib/mongo/protocol/compressed.rb +51 -5
  62. data/lib/mongo/protocol/message.rb +20 -2
  63. data/lib/mongo/protocol/msg.rb +38 -13
  64. data/lib/mongo/protocol/query.rb +11 -11
  65. data/lib/mongo/query_cache.rb +30 -0
  66. data/lib/mongo/retryable.rb +1 -1
  67. data/lib/mongo/server/app_metadata.rb +52 -18
  68. data/lib/mongo/server/connection.rb +5 -0
  69. data/lib/mongo/server/connection_base.rb +13 -10
  70. data/lib/mongo/server/connection_pool.rb +6 -2
  71. data/lib/mongo/server/description/features.rb +9 -8
  72. data/lib/mongo/server/description.rb +4 -0
  73. data/lib/mongo/server/monitor/app_metadata.rb +1 -1
  74. data/lib/mongo/server/monitor/connection.rb +9 -10
  75. data/lib/mongo/server/monitor.rb +20 -1
  76. data/lib/mongo/server/pending_connection.rb +24 -6
  77. data/lib/mongo/server/push_monitor.rb +11 -1
  78. data/lib/mongo/server.rb +7 -1
  79. data/lib/mongo/server_selector/secondary_preferred.rb +7 -2
  80. data/lib/mongo/session/session_pool.rb +4 -2
  81. data/lib/mongo/session.rb +2 -2
  82. data/lib/mongo/socket/ssl.rb +8 -0
  83. data/lib/mongo/socket.rb +29 -4
  84. data/lib/mongo/uri/options_mapper.rb +38 -0
  85. data/lib/mongo/utils.rb +15 -0
  86. data/lib/mongo/version.rb +1 -1
  87. data/lib/mongo.rb +23 -0
  88. data/spec/README.md +24 -1
  89. data/spec/integration/auth_spec.rb +25 -15
  90. data/spec/integration/bulk_write_error_message_spec.rb +41 -0
  91. data/spec/integration/change_stream_spec.rb +4 -4
  92. data/spec/integration/command_monitoring_spec.rb +2 -2
  93. data/spec/integration/connection_spec.rb +2 -0
  94. data/spec/integration/docs_examples_spec.rb +8 -1
  95. data/spec/integration/fork_reconnect_spec.rb +4 -1
  96. data/spec/integration/ocsp_verifier_spec.rb +13 -7
  97. data/spec/integration/operation_failure_code_spec.rb +1 -1
  98. data/spec/integration/operation_failure_message_spec.rb +90 -0
  99. data/spec/integration/query_cache_spec.rb +0 -45
  100. data/spec/integration/reconnect_spec.rb +1 -1
  101. data/spec/integration/snappy_compression_spec.rb +25 -0
  102. data/spec/integration/srv_monitoring_spec.rb +1 -1
  103. data/spec/integration/transactions_examples_spec.rb +6 -0
  104. data/spec/integration/zlib_compression_spec.rb +1 -1
  105. data/spec/integration/zstd_compression_spec.rb +26 -0
  106. data/spec/lite_spec_helper.rb +7 -1
  107. data/spec/mongo/address_spec.rb +15 -11
  108. data/spec/mongo/auth/ldap/conversation_spec.rb +1 -1
  109. data/spec/mongo/auth/ldap_spec.rb +5 -1
  110. data/spec/mongo/auth/scram_negotiation_spec.rb +1 -1
  111. data/spec/mongo/auth/scram_spec.rb +1 -1
  112. data/spec/mongo/auth/x509/conversation_spec.rb +3 -3
  113. data/spec/mongo/client_construction_spec.rb +207 -33
  114. data/spec/mongo/client_spec.rb +17 -0
  115. data/spec/mongo/cluster_spec.rb +1 -0
  116. data/spec/mongo/collection/view/explainable_spec.rb +1 -1
  117. data/spec/mongo/collection/view/readable_spec.rb +33 -19
  118. data/spec/mongo/collection_crud_spec.rb +4357 -0
  119. data/spec/mongo/collection_ddl_spec.rb +534 -0
  120. data/spec/mongo/collection_spec.rb +5 -4859
  121. data/spec/mongo/database_spec.rb +66 -4
  122. data/spec/mongo/error/bulk_write_error_spec.rb +3 -3
  123. data/spec/mongo/error/parser_spec.rb +37 -6
  124. data/spec/mongo/index/view_spec.rb +4 -0
  125. data/spec/mongo/monitoring/event/server_heartbeat_failed_spec.rb +1 -1
  126. data/spec/mongo/monitoring/event/server_heartbeat_succeeded_spec.rb +1 -1
  127. data/spec/mongo/operation/aggregate_spec.rb +2 -1
  128. data/spec/mongo/operation/collections_info_spec.rb +4 -1
  129. data/spec/mongo/operation/command_spec.rb +6 -3
  130. data/spec/mongo/operation/create_index_spec.rb +6 -3
  131. data/spec/mongo/operation/create_user_spec.rb +6 -3
  132. data/spec/mongo/operation/delete/bulk_spec.rb +9 -6
  133. data/spec/mongo/operation/delete_spec.rb +11 -7
  134. data/spec/mongo/operation/drop_index_spec.rb +6 -2
  135. data/spec/mongo/operation/find/legacy_spec.rb +3 -1
  136. data/spec/mongo/operation/get_more_spec.rb +3 -1
  137. data/spec/mongo/operation/indexes_spec.rb +5 -1
  138. data/spec/mongo/operation/insert/bulk_spec.rb +10 -7
  139. data/spec/mongo/operation/insert_spec.rb +15 -12
  140. data/spec/mongo/operation/map_reduce_spec.rb +5 -2
  141. data/spec/mongo/operation/read_preference_legacy_spec.rb +19 -9
  142. data/spec/mongo/operation/read_preference_op_msg_spec.rb +3 -3
  143. data/spec/mongo/operation/remove_user_spec.rb +6 -3
  144. data/spec/mongo/operation/result_spec.rb +1 -1
  145. data/spec/mongo/operation/update/bulk_spec.rb +9 -6
  146. data/spec/mongo/operation/update_spec.rb +10 -7
  147. data/spec/mongo/operation/update_user_spec.rb +4 -1
  148. data/spec/mongo/protocol/compressed_spec.rb +26 -12
  149. data/spec/mongo/query_cache_middleware_spec.rb +55 -0
  150. data/spec/mongo/retryable_spec.rb +3 -2
  151. data/spec/mongo/server/app_metadata_shared.rb +7 -33
  152. data/spec/mongo/server/app_metadata_spec.rb +2 -0
  153. data/spec/mongo/server/connection_pool/populator_spec.rb +3 -1
  154. data/spec/mongo/server/connection_pool_spec.rb +1 -1
  155. data/spec/mongo/server/connection_spec.rb +24 -17
  156. data/spec/mongo/server/monitor/connection_spec.rb +17 -7
  157. data/spec/mongo/server/monitor_spec.rb +9 -1
  158. data/spec/mongo/server_selector/secondary_preferred_spec.rb +6 -6
  159. data/spec/mongo/server_spec.rb +15 -2
  160. data/spec/mongo/socket/ssl_spec.rb +40 -0
  161. data/spec/mongo/socket_spec.rb +2 -2
  162. data/spec/mongo/tls_context_hooks_spec.rb +37 -0
  163. data/spec/runners/connection_string.rb +0 -4
  164. data/spec/runners/crud/requirement.rb +40 -3
  165. data/spec/runners/crud/verifier.rb +8 -0
  166. data/spec/runners/transactions/operation.rb +1 -1
  167. data/spec/runners/transactions/test.rb +1 -0
  168. data/spec/runners/unified/assertions.rb +249 -0
  169. data/spec/runners/unified/change_stream_operations.rb +26 -0
  170. data/spec/runners/unified/crud_operations.rb +199 -0
  171. data/spec/runners/unified/ddl_operations.rb +96 -0
  172. data/spec/runners/unified/entity_map.rb +39 -0
  173. data/spec/runners/unified/error.rb +25 -0
  174. data/spec/runners/unified/event_subscriber.rb +91 -0
  175. data/spec/runners/unified/exceptions.rb +21 -0
  176. data/spec/runners/unified/grid_fs_operations.rb +55 -0
  177. data/spec/runners/unified/support_operations.rb +250 -0
  178. data/spec/runners/unified/test.rb +393 -0
  179. data/spec/runners/unified/test_group.rb +28 -0
  180. data/spec/runners/unified/using_hash.rb +31 -0
  181. data/spec/runners/unified.rb +96 -0
  182. data/spec/shared/lib/mrss/cluster_config.rb +0 -3
  183. data/spec/shared/lib/mrss/docker_runner.rb +0 -3
  184. data/spec/shared/lib/mrss/lite_constraints.rb +0 -16
  185. data/spec/shared/lib/mrss/server_version_registry.rb +0 -3
  186. data/spec/shared/lib/mrss/spec_organizer.rb +0 -3
  187. data/spec/shared/shlib/server.sh +1 -1
  188. data/spec/spec_helper.rb +4 -1
  189. data/spec/spec_tests/crud_unified_spec.rb +10 -0
  190. data/spec/spec_tests/data/change_streams/change-streams.yml +0 -1
  191. data/spec/spec_tests/data/crud_unified/estimatedDocumentCount.yml +267 -0
  192. data/spec/spec_tests/data/retryable_reads/estimatedDocumentCount-4.9.yml +60 -0
  193. data/spec/spec_tests/data/retryable_reads/{estimatedDocumentCount.yml → estimatedDocumentCount-pre4.9.yml} +2 -0
  194. data/spec/spec_tests/data/retryable_reads/estimatedDocumentCount-serverErrors-4.9.yml +146 -0
  195. data/spec/spec_tests/data/retryable_reads/{estimatedDocumentCount-serverErrors.yml → estimatedDocumentCount-serverErrors-pre4.9.yml} +2 -0
  196. data/spec/spec_tests/data/retryable_reads/listIndexNames.yml +1 -1
  197. data/spec/spec_tests/data/unified/valid-fail/operation-failure.yml +31 -0
  198. data/spec/spec_tests/data/unified/valid-pass/poc-change-streams.yml +220 -0
  199. data/spec/spec_tests/data/unified/valid-pass/poc-command-monitoring.yml +102 -0
  200. data/spec/spec_tests/data/unified/valid-pass/poc-crud.yml +184 -0
  201. data/spec/spec_tests/data/unified/valid-pass/poc-gridfs.yml +155 -0
  202. data/spec/spec_tests/data/unified/valid-pass/poc-retryable-reads.yml +193 -0
  203. data/spec/spec_tests/data/unified/valid-pass/poc-retryable-writes.yml +210 -0
  204. data/spec/spec_tests/data/unified/valid-pass/poc-sessions.yml +215 -0
  205. data/spec/spec_tests/data/unified/valid-pass/poc-transactions-convenient-api.yml +235 -0
  206. data/spec/spec_tests/data/unified/valid-pass/poc-transactions-mongos-pin-auto.yml +169 -0
  207. data/spec/spec_tests/data/unified/valid-pass/poc-transactions.yml +170 -0
  208. data/spec/spec_tests/data/uri_options/compression-options.yml +1 -1
  209. data/spec/spec_tests/data/versioned_api/crud-api-version-1-strict.yml +416 -0
  210. data/spec/spec_tests/data/versioned_api/crud-api-version-1.yml +409 -0
  211. data/spec/spec_tests/data/versioned_api/runcommand-helper-no-api-version-declared.yml +67 -0
  212. data/spec/spec_tests/data/versioned_api/test-commands-deprecation-errors.yml +47 -0
  213. data/spec/spec_tests/data/versioned_api/test-commands-strict-mode.yml +44 -0
  214. data/spec/spec_tests/data/versioned_api/transaction-handling.yml +180 -0
  215. data/spec/spec_tests/unified_spec.rb +15 -0
  216. data/spec/spec_tests/uri_options_spec.rb +16 -0
  217. data/spec/spec_tests/versioned_api_spec.rb +10 -0
  218. data/spec/support/client_registry.rb +4 -8
  219. data/spec/support/client_registry_macros.rb +4 -4
  220. data/spec/support/common_shortcuts.rb +15 -1
  221. data/spec/support/shared/session.rb +2 -2
  222. data/spec/support/spec_config.rb +42 -11
  223. data/spec/support/utils.rb +64 -3
  224. data.tar.gz.sig +0 -0
  225. metadata +1005 -915
  226. metadata.gz.sig +0 -0
  227. data/lib/mongo/operation/shared/collections_info_or_list_collections.rb +0 -58
  228. data/lib/mongo/operation/shared/op_msg_or_list_indexes_command.rb +0 -47
  229. data/spec/integration/secondary_reads_spec.rb +0 -102
  230. data/spec/support/cluster_config.rb +0 -207
@@ -0,0 +1,4357 @@
1
+ require 'spec_helper'
2
+
3
+ describe Mongo::Collection do
4
+
5
+ let(:subscriber) { EventSubscriber.new }
6
+
7
+ let(:client) do
8
+ authorized_client.tap do |client|
9
+ client.subscribe(Mongo::Monitoring::COMMAND, subscriber)
10
+ end
11
+ end
12
+
13
+ let(:authorized_collection) { client['collection_spec'] }
14
+
15
+ before do
16
+ authorized_client['collection_spec'].drop
17
+ end
18
+
19
+ let(:collection_invalid_write_concern) do
20
+ authorized_collection.client.with(write: INVALID_WRITE_CONCERN)[authorized_collection.name]
21
+ end
22
+
23
+ let(:collection_with_validator) do
24
+ authorized_client[:validating]
25
+ end
26
+
27
+ describe '#find' do
28
+
29
+ describe 'updating cluster time' do
30
+
31
+ let(:operation) do
32
+ client[TEST_COLL].find.first
33
+ end
34
+
35
+ let(:operation_with_session) do
36
+ client[TEST_COLL].find({}, session: session).first
37
+ end
38
+
39
+ let(:second_operation) do
40
+ client[TEST_COLL].find({}, session: session).first
41
+ end
42
+
43
+ it_behaves_like 'an operation updating cluster time'
44
+ end
45
+
46
+ context 'when provided a filter' do
47
+
48
+ let(:view) do
49
+ authorized_collection.find(name: 1)
50
+ end
51
+
52
+ it 'returns a authorized_collection view for the filter' do
53
+ expect(view.filter).to eq('name' => 1)
54
+ end
55
+ end
56
+
57
+ context 'when provided no filter' do
58
+
59
+ let(:view) do
60
+ authorized_collection.find
61
+ end
62
+
63
+ it 'returns a authorized_collection view with an empty filter' do
64
+ expect(view.filter).to be_empty
65
+ end
66
+ end
67
+
68
+ context 'when providing a bad filter' do
69
+
70
+ let(:view) do
71
+ authorized_collection.find('$or' => [])
72
+ end
73
+
74
+ it 'raises an exception when iterating' do
75
+ expect {
76
+ view.to_a
77
+ }.to raise_exception(Mongo::Error::OperationFailure)
78
+ end
79
+ end
80
+
81
+ context 'when iterating the authorized_collection view' do
82
+
83
+ before do
84
+ authorized_collection.insert_many([{ field: 'test1' }, { field: 'test2' }])
85
+ end
86
+
87
+ let(:view) do
88
+ authorized_collection.find
89
+ end
90
+
91
+ it 'iterates over the documents' do
92
+ view.each do |document|
93
+ expect(document).to_not be_nil
94
+ end
95
+ end
96
+ end
97
+
98
+ context 'when the user is not authorized' do
99
+ require_auth
100
+
101
+ let(:view) do
102
+ unauthorized_collection.find
103
+ end
104
+
105
+ it 'iterates over the documents' do
106
+ expect {
107
+ view.each{ |document| document }
108
+ }.to raise_error(Mongo::Error::OperationFailure)
109
+ end
110
+ end
111
+
112
+ context 'when documents contain potential error message fields' do
113
+
114
+ [ 'errmsg', 'error', Mongo::Operation::Result::OK ].each do |field|
115
+
116
+ context "when the document contains a '#{field}' field" do
117
+
118
+ let(:value) do
119
+ 'testing'
120
+ end
121
+
122
+ let(:view) do
123
+ authorized_collection.find
124
+ end
125
+
126
+ before do
127
+ authorized_collection.insert_one({ field => value })
128
+ end
129
+
130
+ it 'iterates over the documents' do
131
+ view.each do |document|
132
+ expect(document[field]).to eq(value)
133
+ end
134
+ end
135
+ end
136
+ end
137
+ end
138
+
139
+ context 'when provided options' do
140
+
141
+ context 'when a session is provided' do
142
+ require_wired_tiger
143
+
144
+ let(:operation) do
145
+ authorized_collection.find({}, session: session).to_a
146
+ end
147
+
148
+ let(:session) do
149
+ authorized_client.start_session
150
+ end
151
+
152
+ let(:failed_operation) do
153
+ client[authorized_collection.name].find({ '$._id' => 1 }, session: session).to_a
154
+ end
155
+
156
+ let(:client) do
157
+ authorized_client
158
+ end
159
+
160
+ it_behaves_like 'an operation using a session'
161
+ it_behaves_like 'a failed operation using a session'
162
+ end
163
+
164
+ context 'session id' do
165
+ min_server_fcv '3.6'
166
+ require_topology :replica_set, :sharded
167
+ require_wired_tiger
168
+
169
+ let(:options) do
170
+ { session: session }
171
+ end
172
+
173
+ let(:session) do
174
+ client.start_session
175
+ end
176
+
177
+ let(:view) do
178
+ Mongo::Collection::View.new(client[TEST_COLL], selector, view_options)
179
+ end
180
+
181
+ let(:command) do
182
+ client[TEST_COLL].find({}, session: session).explain
183
+ subscriber.started_events.find { |c| c.command_name == 'explain' }.command
184
+ end
185
+
186
+ it 'sends the session id' do
187
+ expect(command['lsid']).to eq(session.session_id)
188
+ end
189
+ end
190
+
191
+ context 'when a session supporting causal consistency is used' do
192
+ require_wired_tiger
193
+
194
+ let(:operation) do
195
+ collection.find({}, session: session).to_a
196
+ end
197
+
198
+ let(:command) do
199
+ operation
200
+ subscriber.started_events.find { |cmd| cmd.command_name == 'find' }.command
201
+ end
202
+
203
+ it_behaves_like 'an operation supporting causally consistent reads'
204
+ end
205
+
206
+ let(:view) do
207
+ authorized_collection.find({}, options)
208
+ end
209
+
210
+ context 'when provided :allow_partial_results' do
211
+
212
+ let(:options) do
213
+ { allow_partial_results: true }
214
+ end
215
+
216
+ it 'returns a view with :allow_partial_results set' do
217
+ expect(view.options[:allow_partial_results]).to be(options[:allow_partial_results])
218
+ end
219
+ end
220
+
221
+ context 'when provided :batch_size' do
222
+
223
+ let(:options) do
224
+ { batch_size: 100 }
225
+ end
226
+
227
+ it 'returns a view with :batch_size set' do
228
+ expect(view.options[:batch_size]).to eq(options[:batch_size])
229
+ end
230
+ end
231
+
232
+ context 'when provided :comment' do
233
+
234
+ let(:options) do
235
+ { comment: 'slow query' }
236
+ end
237
+
238
+ it 'returns a view with :comment set' do
239
+ expect(view.modifiers[:$comment]).to eq(options[:comment])
240
+ end
241
+ end
242
+
243
+ context 'when provided :cursor_type' do
244
+
245
+ let(:options) do
246
+ { cursor_type: :tailable }
247
+ end
248
+
249
+ it 'returns a view with :cursor_type set' do
250
+ expect(view.options[:cursor_type]).to eq(options[:cursor_type])
251
+ end
252
+ end
253
+
254
+ context 'when provided :max_time_ms' do
255
+
256
+ let(:options) do
257
+ { max_time_ms: 500 }
258
+ end
259
+
260
+ it 'returns a view with :max_time_ms set' do
261
+ expect(view.modifiers[:$maxTimeMS]).to eq(options[:max_time_ms])
262
+ end
263
+ end
264
+
265
+ context 'when provided :modifiers' do
266
+
267
+ let(:options) do
268
+ { modifiers: { '$orderby' => Mongo::Index::ASCENDING } }
269
+ end
270
+
271
+ it 'returns a view with modifiers set' do
272
+ expect(view.modifiers).to eq(options[:modifiers])
273
+ end
274
+
275
+ it 'dups the modifiers hash' do
276
+ expect(view.modifiers).not_to be(options[:modifiers])
277
+ end
278
+ end
279
+
280
+ context 'when provided :no_cursor_timeout' do
281
+
282
+ let(:options) do
283
+ { no_cursor_timeout: true }
284
+ end
285
+
286
+ it 'returns a view with :no_cursor_timeout set' do
287
+ expect(view.options[:no_cursor_timeout]).to eq(options[:no_cursor_timeout])
288
+ end
289
+ end
290
+
291
+ context 'when provided :oplog_replay' do
292
+
293
+ let(:options) do
294
+ { oplog_replay: false }
295
+ end
296
+
297
+ it 'returns a view with :oplog_replay set' do
298
+ expect(view.options[:oplog_replay]).to eq(options[:oplog_replay])
299
+ end
300
+ end
301
+
302
+ context 'when provided :projection' do
303
+
304
+ let(:options) do
305
+ { projection: { 'x' => 1 } }
306
+ end
307
+
308
+ it 'returns a view with :projection set' do
309
+ expect(view.options[:projection]).to eq(options[:projection])
310
+ end
311
+ end
312
+
313
+ context 'when provided :skip' do
314
+
315
+ let(:options) do
316
+ { skip: 5 }
317
+ end
318
+
319
+ it 'returns a view with :skip set' do
320
+ expect(view.options[:skip]).to eq(options[:skip])
321
+ end
322
+ end
323
+
324
+ context 'when provided :sort' do
325
+
326
+ let(:options) do
327
+ { sort: { 'x' => Mongo::Index::ASCENDING } }
328
+ end
329
+
330
+ it 'returns a view with :sort set' do
331
+ expect(view.modifiers[:$orderby]).to eq(options[:sort])
332
+ end
333
+ end
334
+
335
+ context 'when provided :collation' do
336
+
337
+ let(:options) do
338
+ { collation: { 'locale' => 'en_US' } }
339
+ end
340
+
341
+ it 'returns a view with :collation set' do
342
+ expect(view.options[:collation]).to eq(options[:collation])
343
+ end
344
+ end
345
+ end
346
+ end
347
+
348
+ describe '#insert_many' do
349
+
350
+ let(:result) do
351
+ authorized_collection.insert_many([{ name: 'test1' }, { name: 'test2' }])
352
+ end
353
+
354
+ it 'inserts the documents into the collection' do
355
+ expect(result.inserted_count).to eq(2)
356
+ end
357
+
358
+ it 'contains the ids in the result' do
359
+ expect(result.inserted_ids.size).to eq(2)
360
+ end
361
+
362
+ context 'when a session is provided' do
363
+
364
+ let(:session) do
365
+ authorized_client.start_session
366
+ end
367
+
368
+ let(:operation) do
369
+ authorized_collection.insert_many([{ name: 'test1' }, { name: 'test2' }], session: session)
370
+ end
371
+
372
+ let(:failed_operation) do
373
+ authorized_collection.insert_many([{ _id: 'test1' }, { _id: 'test1' }], session: session)
374
+ end
375
+
376
+ let(:client) do
377
+ authorized_client
378
+ end
379
+
380
+ it_behaves_like 'an operation using a session'
381
+ it_behaves_like 'a failed operation using a session'
382
+ end
383
+
384
+ context 'when unacknowledged writes is used with an explicit session' do
385
+
386
+ let(:collection_with_unacknowledged_write_concern) do
387
+ authorized_collection.with(write: { w: 0 })
388
+ end
389
+
390
+ let(:operation) do
391
+ collection_with_unacknowledged_write_concern.insert_many([{ name: 'test1' }, { name: 'test2' }], session: session)
392
+ end
393
+
394
+ it_behaves_like 'an explicit session with an unacknowledged write'
395
+ end
396
+
397
+ context 'when unacknowledged writes is used with an implicit session' do
398
+
399
+ let(:collection_with_unacknowledged_write_concern) do
400
+ client.with(write: { w: 0 })[TEST_COLL]
401
+ end
402
+
403
+ let(:operation) do
404
+ collection_with_unacknowledged_write_concern.insert_many([{ name: 'test1' }, { name: 'test2' }])
405
+ end
406
+
407
+ it_behaves_like 'an implicit session with an unacknowledged write'
408
+ end
409
+
410
+ context 'when a document contains invalid keys' do
411
+
412
+ let(:docs) do
413
+ [ { 'first.name' => 'test1' }, { name: 'test2' } ]
414
+ end
415
+
416
+ it 'raises a BSON::String::IllegalKey exception' do
417
+ expect {
418
+ authorized_collection.insert_many(docs)
419
+ }.to raise_exception(BSON::String::IllegalKey)
420
+ end
421
+ end
422
+
423
+ context 'when the client has a custom id generator' do
424
+
425
+ let(:generator) do
426
+ Class.new do
427
+ def generate
428
+ 1
429
+ end
430
+ end.new
431
+ end
432
+
433
+ let(:custom_client) do
434
+ authorized_client.with(id_generator: generator)
435
+ end
436
+
437
+ let(:custom_collection) do
438
+ custom_client['custom_id_generator_test_collection']
439
+ end
440
+
441
+ before do
442
+ custom_collection.delete_many
443
+ custom_collection.insert_many([{ name: 'testing' }])
444
+ expect(custom_collection.count).to eq(1)
445
+ end
446
+
447
+ it 'inserts with the custom id' do
448
+ expect(custom_collection.count).to eq(1)
449
+ expect(custom_collection.find.first[:_id]).to eq(1)
450
+ end
451
+ end
452
+
453
+ context 'when the inserts fail' do
454
+
455
+ let(:result) do
456
+ authorized_collection.insert_many([{ _id: 1 }, { _id: 1 }])
457
+ end
458
+
459
+ it 'raises an BulkWriteError' do
460
+ expect {
461
+ result
462
+ }.to raise_exception(Mongo::Error::BulkWriteError)
463
+ end
464
+ end
465
+
466
+ context "when the documents exceed the max bson size" do
467
+
468
+ let(:documents) do
469
+ [{ '_id' => 1, 'name' => '1'*17000000 }]
470
+ end
471
+
472
+ it 'raises a MaxBSONSize error' do
473
+ expect {
474
+ authorized_collection.insert_many(documents)
475
+ }.to raise_error(Mongo::Error::MaxBSONSize)
476
+ end
477
+ end
478
+
479
+ context 'when the documents are sent with OP_MSG' do
480
+ min_server_fcv '3.6'
481
+
482
+ let(:documents) do
483
+ [{ '_id' => 1, 'name' => '1'*16777191 }, { '_id' => 'y' }]
484
+ end
485
+
486
+ before do
487
+ authorized_collection.insert_many(documents)
488
+ end
489
+
490
+ let(:insert_events) do
491
+ subscriber.started_events.select { |e| e.command_name == 'insert' }
492
+ end
493
+
494
+ it 'sends the documents in one OP_MSG' do
495
+ expect(insert_events.size).to eq(1)
496
+ expect(insert_events[0].command['documents']).to eq(documents)
497
+ end
498
+ end
499
+
500
+ context 'when collection has a validator' do
501
+ min_server_fcv '3.2'
502
+
503
+ around(:each) do |spec|
504
+ authorized_client[:validating].drop
505
+ authorized_client[:validating,
506
+ :validator => { :a => { '$exists' => true } }].tap do |c|
507
+ c.create
508
+ end
509
+ spec.run
510
+ collection_with_validator.drop
511
+ end
512
+
513
+ context 'when the document is valid' do
514
+
515
+ let(:result) do
516
+ collection_with_validator.insert_many([{ a: 1 }, { a: 2 }])
517
+ end
518
+
519
+ it 'inserts successfully' do
520
+ expect(result.inserted_count).to eq(2)
521
+ end
522
+ end
523
+
524
+ context 'when the document is invalid' do
525
+
526
+ context 'when bypass_document_validation is not set' do
527
+
528
+ let(:result2) do
529
+ collection_with_validator.insert_many([{ x: 1 }, { x: 2 }])
530
+ end
531
+
532
+ it 'raises a BulkWriteError' do
533
+ expect {
534
+ result2
535
+ }.to raise_exception(Mongo::Error::BulkWriteError)
536
+ end
537
+ end
538
+
539
+ context 'when bypass_document_validation is true' do
540
+
541
+ let(:result3) do
542
+ collection_with_validator.insert_many(
543
+ [{ x: 1 }, { x: 2 }], :bypass_document_validation => true)
544
+ end
545
+
546
+ it 'inserts successfully' do
547
+ expect(result3.inserted_count).to eq(2)
548
+ end
549
+ end
550
+ end
551
+ end
552
+
553
+ context 'when unacknowledged writes is used' do
554
+
555
+ let(:collection_with_unacknowledged_write_concern) do
556
+ authorized_collection.with(write: { w: 0 })
557
+ end
558
+
559
+ let(:result) do
560
+ collection_with_unacknowledged_write_concern.insert_many([{ _id: 1 }, { _id: 1 }])
561
+ end
562
+
563
+ it 'does not raise an exception' do
564
+ expect(result.inserted_count).to be(0)
565
+ end
566
+ end
567
+
568
+ context 'when various options passed in' do
569
+ # w: 2 requires a replica set
570
+ require_topology :replica_set
571
+
572
+ # https://jira.mongodb.org/browse/RUBY-2306
573
+ min_server_fcv '3.6'
574
+
575
+ let(:session) do
576
+ authorized_client.start_session
577
+ end
578
+
579
+ let(:events) do
580
+ subscriber.command_started_events('insert')
581
+ end
582
+
583
+ let(:collection) do
584
+ authorized_collection.with(write_concern: {w: 2})
585
+ end
586
+
587
+ let!(:command) do
588
+ Utils.get_command_event(authorized_client, 'insert') do |client|
589
+ collection.insert_many([{ name: 'test1' }, { name: 'test2' }], session: session,
590
+ write_concern: {w: 1}, bypass_document_validation: true)
591
+ end.command
592
+ end
593
+
594
+ it 'inserts many successfully with correct options sent to server' do
595
+ expect(events.length).to eq(1)
596
+ expect(command[:writeConcern]).to_not be_nil
597
+ expect(command[:writeConcern][:w]).to eq(1)
598
+ expect(command[:bypassDocumentValidation]).to be(true)
599
+ end
600
+ end
601
+ end
602
+
603
+ describe '#insert_one' do
604
+
605
+ describe 'updating cluster time' do
606
+
607
+ let(:operation) do
608
+ client[TEST_COLL].insert_one({ name: 'testing' })
609
+ end
610
+
611
+ let(:operation_with_session) do
612
+ client[TEST_COLL].insert_one({ name: 'testing' }, session: session)
613
+ end
614
+
615
+ let(:second_operation) do
616
+ client[TEST_COLL].insert_one({ name: 'testing' }, session: session)
617
+ end
618
+
619
+ it_behaves_like 'an operation updating cluster time'
620
+ end
621
+
622
+ let(:result) do
623
+ authorized_collection.insert_one({ name: 'testing' })
624
+ end
625
+
626
+ it 'inserts the document into the collection'do
627
+ expect(result.written_count).to eq(1)
628
+ end
629
+
630
+ it 'contains the id in the result' do
631
+ expect(result.inserted_id).to_not be_nil
632
+ end
633
+
634
+ context 'when a session is provided' do
635
+
636
+ let(:session) do
637
+ authorized_client.start_session
638
+ end
639
+
640
+ let(:operation) do
641
+ authorized_collection.insert_one({ name: 'testing' }, session: session)
642
+ end
643
+
644
+ let(:failed_operation) do
645
+ authorized_collection.insert_one({ _id: 'testing' })
646
+ authorized_collection.insert_one({ _id: 'testing' }, session: session)
647
+ end
648
+
649
+ let(:client) do
650
+ authorized_client
651
+ end
652
+
653
+ it_behaves_like 'an operation using a session'
654
+ it_behaves_like 'a failed operation using a session'
655
+ end
656
+
657
+ context 'when unacknowledged writes is used with an explicit session' do
658
+
659
+ let(:collection_with_unacknowledged_write_concern) do
660
+ authorized_collection.with(write: { w: 0 })
661
+ end
662
+
663
+ let(:operation) do
664
+ collection_with_unacknowledged_write_concern.insert_one({ name: 'testing' }, session: session)
665
+ end
666
+
667
+ it_behaves_like 'an explicit session with an unacknowledged write'
668
+ end
669
+
670
+ context 'when unacknowledged writes is used with an implicit session' do
671
+
672
+ let(:collection_with_unacknowledged_write_concern) do
673
+ client.with(write: { w: 0 })[TEST_COLL]
674
+ end
675
+
676
+ let(:operation) do
677
+ collection_with_unacknowledged_write_concern.insert_one({ name: 'testing' })
678
+ end
679
+
680
+ it_behaves_like 'an implicit session with an unacknowledged write'
681
+ end
682
+
683
+ context 'when various options passed in' do
684
+ # https://jira.mongodb.org/browse/RUBY-2306
685
+ min_server_fcv '3.6'
686
+
687
+ let(:session) do
688
+ authorized_client.start_session
689
+ end
690
+
691
+ let(:events) do
692
+ subscriber.command_started_events('insert')
693
+ end
694
+
695
+ let(:collection) do
696
+ authorized_collection.with(write_concern: {w: 3})
697
+ end
698
+
699
+ let!(:command) do
700
+ Utils.get_command_event(authorized_client, 'insert') do |client|
701
+ collection.insert_one({name: 'test1'}, session: session, write_concern: {w: 1},
702
+ bypass_document_validation: true)
703
+ end.command
704
+ end
705
+
706
+ it 'inserts one successfully with correct options sent to server' do
707
+ expect(events.length).to eq(1)
708
+ expect(command[:writeConcern]).to_not be_nil
709
+ expect(command[:writeConcern][:w]).to eq(1)
710
+ expect(command[:bypassDocumentValidation]).to be(true)
711
+ end
712
+ end
713
+
714
+ context 'when the document contains invalid keys' do
715
+
716
+ let(:doc) do
717
+ { 'testing.test' => 'value' }
718
+ end
719
+
720
+ it 'raises a BSON::String::IllegalKey exception' do
721
+ expect {
722
+ authorized_collection.insert_one(doc)
723
+ }.to raise_exception(BSON::String::IllegalKey)
724
+ end
725
+ end
726
+
727
+ context 'when the document is nil' do
728
+ let(:result) do
729
+ authorized_collection.insert_one(nil)
730
+ end
731
+
732
+ it 'raises an ArgumentError' do
733
+ expect {
734
+ result
735
+ }.to raise_error(ArgumentError, "Document to be inserted cannot be nil")
736
+ end
737
+ end
738
+
739
+ context 'when the insert fails' do
740
+
741
+ let(:result) do
742
+ authorized_collection.insert_one(_id: 1)
743
+ authorized_collection.insert_one(_id: 1)
744
+ end
745
+
746
+ it 'raises an OperationFailure' do
747
+ expect {
748
+ result
749
+ }.to raise_exception(Mongo::Error::OperationFailure)
750
+ end
751
+ end
752
+
753
+ context 'when the client has a custom id generator' do
754
+
755
+ let(:generator) do
756
+ Class.new do
757
+ def generate
758
+ 1
759
+ end
760
+ end.new
761
+ end
762
+
763
+ let(:custom_client) do
764
+ authorized_client.with(id_generator: generator)
765
+ end
766
+
767
+ let(:custom_collection) do
768
+ custom_client[TEST_COLL]
769
+ end
770
+
771
+ before do
772
+ custom_collection.delete_many
773
+ custom_collection.insert_one({ name: 'testing' })
774
+ end
775
+
776
+ it 'inserts with the custom id' do
777
+ expect(custom_collection.find.first[:_id]).to eq(1)
778
+ end
779
+ end
780
+
781
+ context 'when collection has a validator' do
782
+ min_server_fcv '3.2'
783
+
784
+ around(:each) do |spec|
785
+ authorized_client[:validating,
786
+ :validator => { :a => { '$exists' => true } }].tap do |c|
787
+ c.create
788
+ end
789
+ spec.run
790
+ collection_with_validator.drop
791
+ end
792
+
793
+ context 'when the document is valid' do
794
+
795
+ let(:result) do
796
+ collection_with_validator.insert_one({ a: 1 })
797
+ end
798
+
799
+ it 'inserts successfully' do
800
+ expect(result.written_count).to eq(1)
801
+ end
802
+ end
803
+
804
+ context 'when the document is invalid' do
805
+
806
+ context 'when bypass_document_validation is not set' do
807
+
808
+ let(:result2) do
809
+ collection_with_validator.insert_one({ x: 1 })
810
+ end
811
+
812
+ it 'raises a OperationFailure' do
813
+ expect {
814
+ result2
815
+ }.to raise_exception(Mongo::Error::OperationFailure)
816
+ end
817
+ end
818
+
819
+ context 'when bypass_document_validation is true' do
820
+
821
+ let(:result3) do
822
+ collection_with_validator.insert_one(
823
+ { x: 1 }, :bypass_document_validation => true)
824
+ end
825
+
826
+ it 'inserts successfully' do
827
+ expect(result3.written_count).to eq(1)
828
+ end
829
+ end
830
+ end
831
+ end
832
+ end
833
+
834
+ describe '#bulk_write' do
835
+
836
+ context 'when various options passed in' do
837
+ min_server_fcv '3.2'
838
+ require_topology :replica_set
839
+
840
+ # https://jira.mongodb.org/browse/RUBY-2306
841
+ min_server_fcv '3.6'
842
+
843
+ let(:requests) do
844
+ [
845
+ { insert_one: { name: "anne" }},
846
+ { insert_one: { name: "bob" }},
847
+ { insert_one: { name: "charlie" }}
848
+ ]
849
+ end
850
+
851
+ let(:session) do
852
+ authorized_client.start_session
853
+ end
854
+
855
+ let!(:command) do
856
+ Utils.get_command_event(authorized_client, 'insert') do |client|
857
+ collection.bulk_write(requests, session: session, write_concern: {w: 1},
858
+ bypass_document_validation: true)
859
+ end.command
860
+ end
861
+
862
+ let(:events) do
863
+ subscriber.command_started_events('insert')
864
+ end
865
+
866
+ let(:collection) do
867
+ authorized_collection.with(write_concern: {w: 2})
868
+ end
869
+
870
+ it 'inserts successfully with correct options sent to server' do
871
+ expect(collection.count).to eq(3)
872
+ expect(events.length).to eq(1)
873
+ expect(command[:writeConcern]).to_not be_nil
874
+ expect(command[:writeConcern][:w]).to eq(1)
875
+ expect(command[:bypassDocumentValidation]).to eq(true)
876
+ end
877
+ end
878
+ end
879
+
880
+ describe '#aggregate' do
881
+
882
+ describe 'updating cluster time' do
883
+
884
+ let(:operation) do
885
+ client[TEST_COLL].aggregate([]).first
886
+ end
887
+
888
+ let(:operation_with_session) do
889
+ client[TEST_COLL].aggregate([], session: session).first
890
+ end
891
+
892
+ let(:second_operation) do
893
+ client[TEST_COLL].aggregate([], session: session).first
894
+ end
895
+
896
+ it_behaves_like 'an operation updating cluster time'
897
+ end
898
+
899
+ context 'when a session supporting causal consistency is used' do
900
+ require_wired_tiger
901
+
902
+ let(:operation) do
903
+ collection.aggregate([], session: session).first
904
+ end
905
+
906
+ let(:command) do
907
+ operation
908
+ subscriber.started_events.find { |cmd| cmd.command_name == 'aggregate' }.command
909
+ end
910
+
911
+ it_behaves_like 'an operation supporting causally consistent reads'
912
+ end
913
+
914
+ it 'returns an Aggregation object' do
915
+ expect(authorized_collection.aggregate([])).to be_a(Mongo::Collection::View::Aggregation)
916
+ end
917
+
918
+ context 'when options are provided' do
919
+
920
+ let(:options) do
921
+ { :allow_disk_use => true, :bypass_document_validation => true }
922
+ end
923
+
924
+ it 'sets the options on the Aggregation object' do
925
+ expect(authorized_collection.aggregate([], options).options).to eq(BSON::Document.new(options))
926
+ end
927
+
928
+ context 'when the :comment option is provided' do
929
+
930
+ let(:options) do
931
+ { :comment => 'testing' }
932
+ end
933
+
934
+ it 'sets the options on the Aggregation object' do
935
+ expect(authorized_collection.aggregate([], options).options).to eq(BSON::Document.new(options))
936
+ end
937
+ end
938
+
939
+ context 'when a session is provided' do
940
+
941
+ let(:session) do
942
+ authorized_client.start_session
943
+ end
944
+
945
+ let(:operation) do
946
+ authorized_collection.aggregate([], session: session).to_a
947
+ end
948
+
949
+ let(:failed_operation) do
950
+ authorized_collection.aggregate([ { '$invalid' => 1 }], session: session).to_a
951
+ end
952
+
953
+ let(:client) do
954
+ authorized_client
955
+ end
956
+
957
+ it_behaves_like 'an operation using a session'
958
+ it_behaves_like 'a failed operation using a session'
959
+ end
960
+
961
+ context 'when a hint is provided' do
962
+
963
+ let(:options) do
964
+ { 'hint' => { 'y' => 1 } }
965
+ end
966
+
967
+ it 'sets the options on the Aggregation object' do
968
+ expect(authorized_collection.aggregate([], options).options).to eq(options)
969
+ end
970
+ end
971
+
972
+ context 'when collation is provided' do
973
+
974
+ before do
975
+ authorized_collection.insert_many([ { name: 'bang' }, { name: 'bang' }])
976
+ end
977
+
978
+ let(:pipeline) do
979
+ [{ "$match" => { "name" => "BANG" } }]
980
+ end
981
+
982
+ let(:options) do
983
+ { collation: { locale: 'en_US', strength: 2 } }
984
+ end
985
+
986
+ let(:result) do
987
+ authorized_collection.aggregate(pipeline, options).collect { |doc| doc['name']}
988
+ end
989
+
990
+ context 'when the server selected supports collations' do
991
+ min_server_fcv '3.4'
992
+
993
+ it 'applies the collation' do
994
+ expect(result).to eq(['bang', 'bang'])
995
+ end
996
+ end
997
+
998
+ context 'when the server selected does not support collations' do
999
+ max_server_version '3.2'
1000
+
1001
+ it 'raises an exception' do
1002
+ expect {
1003
+ result
1004
+ }.to raise_exception(Mongo::Error::UnsupportedCollation)
1005
+ end
1006
+
1007
+ context 'when a String key is used' do
1008
+
1009
+ let(:options) do
1010
+ { 'collation' => { locale: 'en_US', strength: 2 } }
1011
+ end
1012
+
1013
+ it 'raises an exception' do
1014
+ expect {
1015
+ result
1016
+ }.to raise_exception(Mongo::Error::UnsupportedCollation)
1017
+ end
1018
+ end
1019
+ end
1020
+ end
1021
+ end
1022
+ end
1023
+
1024
+ describe '#count_documents' do
1025
+
1026
+ before do
1027
+ authorized_collection.delete_many
1028
+ end
1029
+
1030
+ context 'no argument provided' do
1031
+
1032
+ context 'when collection is empty' do
1033
+ it 'returns 0 matching documents' do
1034
+ expect(authorized_collection.count_documents).to eq(0)
1035
+ end
1036
+ end
1037
+
1038
+ context 'when collection is not empty' do
1039
+
1040
+ let(:documents) do
1041
+ documents = []
1042
+ 1.upto(10) do |index|
1043
+ documents << { key: 'a', _id: "in#{index}" }
1044
+ end
1045
+ documents
1046
+ end
1047
+
1048
+ before do
1049
+ authorized_collection.insert_many(documents)
1050
+ end
1051
+
1052
+ it 'returns 10 matching documents' do
1053
+ expect(authorized_collection.count_documents).to eq(10)
1054
+ end
1055
+ end
1056
+ end
1057
+
1058
+ context 'when transactions are enabled' do
1059
+ require_wired_tiger
1060
+ require_transaction_support
1061
+
1062
+ before do
1063
+ # Ensure that the collection is created
1064
+ authorized_collection.insert_one(x: 1)
1065
+ authorized_collection.delete_many({})
1066
+ end
1067
+
1068
+ let(:session) do
1069
+ authorized_client.start_session
1070
+ end
1071
+
1072
+ it 'successfully starts a transaction and executes a transaction' do
1073
+ session.start_transaction
1074
+ expect(
1075
+ session.instance_variable_get(:@state)
1076
+ ).to eq(Mongo::Session::STARTING_TRANSACTION_STATE)
1077
+
1078
+ expect(authorized_collection.count_documents({}, { session: session })).to eq(0)
1079
+ expect(
1080
+ session.instance_variable_get(:@state)
1081
+ ).to eq(Mongo::Session::TRANSACTION_IN_PROGRESS_STATE)
1082
+
1083
+ authorized_collection.insert_one({ x: 1 }, { session: session })
1084
+ expect(authorized_collection.count_documents({}, { session: session })).to eq(1)
1085
+
1086
+ session.commit_transaction
1087
+ expect(
1088
+ session.instance_variable_get(:@state)
1089
+ ).to eq(Mongo::Session::TRANSACTION_COMMITTED_STATE)
1090
+ end
1091
+ end
1092
+ end
1093
+
1094
+ describe '#count' do
1095
+
1096
+ let(:documents) do
1097
+ (1..10).map{ |i| { field: "test#{i}" }}
1098
+ end
1099
+
1100
+ before do
1101
+ authorized_collection.insert_many(documents)
1102
+ end
1103
+
1104
+ it 'returns an integer count' do
1105
+ expect(authorized_collection.count).to eq(10)
1106
+ end
1107
+
1108
+ context 'when options are provided' do
1109
+
1110
+ it 'passes the options to the count' do
1111
+ expect(authorized_collection.count({}, limit: 5)).to eq(5)
1112
+ end
1113
+
1114
+ context 'when a session is provided' do
1115
+ require_wired_tiger
1116
+
1117
+ let(:session) do
1118
+ authorized_client.start_session
1119
+ end
1120
+
1121
+ let(:operation) do
1122
+ authorized_collection.count({}, session: session)
1123
+ end
1124
+
1125
+ let(:failed_operation) do
1126
+ authorized_collection.count({ '$._id' => 1 }, session: session)
1127
+ end
1128
+
1129
+ let(:client) do
1130
+ authorized_client
1131
+ end
1132
+
1133
+ it_behaves_like 'an operation using a session'
1134
+ it_behaves_like 'a failed operation using a session'
1135
+ end
1136
+
1137
+ context 'when a session supporting causal consistency is used' do
1138
+ require_wired_tiger
1139
+
1140
+ let(:operation) do
1141
+ collection.count({}, session: session)
1142
+ end
1143
+
1144
+ let(:command) do
1145
+ operation
1146
+ subscriber.started_events.find { |cmd| cmd.command_name == 'count' }.command
1147
+ end
1148
+
1149
+ it_behaves_like 'an operation supporting causally consistent reads'
1150
+ end
1151
+
1152
+ context 'when a collation is specified' do
1153
+
1154
+ let(:selector) do
1155
+ { name: 'BANG' }
1156
+ end
1157
+
1158
+ let(:result) do
1159
+ authorized_collection.count(selector, options)
1160
+ end
1161
+
1162
+ before do
1163
+ authorized_collection.insert_one(name: 'bang')
1164
+ end
1165
+
1166
+ let(:options) do
1167
+ { collation: { locale: 'en_US', strength: 2 } }
1168
+ end
1169
+
1170
+ context 'when the server selected supports collations' do
1171
+ min_server_fcv '3.4'
1172
+
1173
+ it 'applies the collation to the count' do
1174
+ expect(result).to eq(1)
1175
+ end
1176
+ end
1177
+
1178
+ context 'when the server selected does not support collations' do
1179
+ max_server_version '3.2'
1180
+
1181
+ it 'raises an exception' do
1182
+ expect {
1183
+ result
1184
+ }.to raise_exception(Mongo::Error::UnsupportedCollation)
1185
+ end
1186
+
1187
+ context 'when a String key is used' do
1188
+
1189
+ let(:options) do
1190
+ { 'collation' => { locale: 'en_US', strength: 2 } }
1191
+ end
1192
+
1193
+ it 'raises an exception' do
1194
+ expect {
1195
+ result
1196
+ }.to raise_exception(Mongo::Error::UnsupportedCollation)
1197
+ end
1198
+ end
1199
+ end
1200
+ end
1201
+ end
1202
+ end
1203
+
1204
+ describe '#distinct' do
1205
+
1206
+ let(:documents) do
1207
+ (1..3).map{ |i| { field: "test#{i}" }}
1208
+ end
1209
+
1210
+ before do
1211
+ authorized_collection.insert_many(documents)
1212
+ end
1213
+
1214
+ it 'returns the distinct values' do
1215
+ expect(authorized_collection.distinct(:field).sort).to eq([ 'test1', 'test2', 'test3' ])
1216
+ end
1217
+
1218
+ context 'when a selector is provided' do
1219
+
1220
+ it 'returns the distinct values' do
1221
+ expect(authorized_collection.distinct(:field, field: 'test1')).to eq([ 'test1' ])
1222
+ end
1223
+ end
1224
+
1225
+ context 'when options are provided' do
1226
+
1227
+ it 'passes the options to the distinct command' do
1228
+ expect(authorized_collection.distinct(:field, {}, max_time_ms: 100).sort).to eq([ 'test1', 'test2', 'test3' ])
1229
+ end
1230
+
1231
+ context 'when a session is provided' do
1232
+ require_wired_tiger
1233
+
1234
+ let(:session) do
1235
+ authorized_client.start_session
1236
+ end
1237
+
1238
+ let(:operation) do
1239
+ authorized_collection.distinct(:field, {}, session: session)
1240
+ end
1241
+
1242
+ let(:failed_operation) do
1243
+ authorized_collection.distinct(:field, { '$._id' => 1 }, session: session)
1244
+ end
1245
+
1246
+ let(:client) do
1247
+ authorized_client
1248
+ end
1249
+
1250
+ it_behaves_like 'an operation using a session'
1251
+ it_behaves_like 'a failed operation using a session'
1252
+ end
1253
+ end
1254
+
1255
+ context 'when a session supporting causal consistency is used' do
1256
+ require_wired_tiger
1257
+
1258
+ let(:operation) do
1259
+ collection.distinct(:field, {}, session: session)
1260
+ end
1261
+
1262
+ let(:command) do
1263
+ operation
1264
+ subscriber.started_events.find { |cmd| cmd.command_name == 'distinct' }.command
1265
+ end
1266
+
1267
+ it_behaves_like 'an operation supporting causally consistent reads'
1268
+ end
1269
+
1270
+ context 'when a collation is specified' do
1271
+
1272
+ let(:result) do
1273
+ authorized_collection.distinct(:name, {}, options)
1274
+ end
1275
+
1276
+ before do
1277
+ authorized_collection.insert_one(name: 'bang')
1278
+ authorized_collection.insert_one(name: 'BANG')
1279
+ end
1280
+
1281
+ let(:options) do
1282
+ { collation: { locale: 'en_US', strength: 2 } }
1283
+ end
1284
+
1285
+ context 'when the server selected supports collations' do
1286
+ min_server_fcv '3.4'
1287
+
1288
+ it 'applies the collation to the distinct' do
1289
+ expect(result).to eq(['bang'])
1290
+ end
1291
+ end
1292
+
1293
+ context 'when the server selected does not support collations' do
1294
+ max_server_version '3.2'
1295
+
1296
+ it 'raises an exception' do
1297
+ expect {
1298
+ result
1299
+ }.to raise_exception(Mongo::Error::UnsupportedCollation)
1300
+ end
1301
+
1302
+ context 'when a String key is used' do
1303
+
1304
+ let(:options) do
1305
+ { 'collation' => { locale: 'en_US', strength: 2 } }
1306
+ end
1307
+
1308
+ it 'raises an exception' do
1309
+ expect {
1310
+ result
1311
+ }.to raise_exception(Mongo::Error::UnsupportedCollation)
1312
+ end
1313
+ end
1314
+ end
1315
+ end
1316
+
1317
+ context 'when a collation is not specified' do
1318
+
1319
+ let(:result) do
1320
+ authorized_collection.distinct(:name)
1321
+ end
1322
+
1323
+ before do
1324
+ authorized_collection.insert_one(name: 'bang')
1325
+ authorized_collection.insert_one(name: 'BANG')
1326
+ end
1327
+
1328
+ it 'does not apply the collation to the distinct' do
1329
+ expect(result).to match_array(['bang', 'BANG'])
1330
+ end
1331
+ end
1332
+ end
1333
+
1334
+ describe '#delete_one' do
1335
+
1336
+ context 'when a selector was provided' do
1337
+
1338
+ let(:selector) do
1339
+ { field: 'test1' }
1340
+ end
1341
+
1342
+ before do
1343
+ authorized_collection.insert_many([
1344
+ { field: 'test1' },
1345
+ { field: 'test1' },
1346
+ { field: 'test1' }
1347
+ ])
1348
+ end
1349
+
1350
+ let(:response) do
1351
+ authorized_collection.delete_one(selector)
1352
+ end
1353
+
1354
+ it 'deletes the first matching document in the collection' do
1355
+ expect(response.deleted_count).to eq(1)
1356
+ end
1357
+ end
1358
+
1359
+ context 'when no selector was provided' do
1360
+
1361
+ before do
1362
+ authorized_collection.insert_many([{ field: 'test1' }, { field: 'test2' }])
1363
+ end
1364
+
1365
+ let(:response) do
1366
+ authorized_collection.delete_one
1367
+ end
1368
+
1369
+ it 'deletes the first document in the collection' do
1370
+ expect(response.deleted_count).to eq(1)
1371
+ end
1372
+ end
1373
+
1374
+ context 'when the delete fails' do
1375
+ require_topology :single
1376
+
1377
+ let(:result) do
1378
+ collection_invalid_write_concern.delete_one
1379
+ end
1380
+
1381
+ it 'raises an OperationFailure' do
1382
+ expect {
1383
+ result
1384
+ }.to raise_exception(Mongo::Error::OperationFailure)
1385
+ end
1386
+ end
1387
+
1388
+ context 'when a session is provided' do
1389
+
1390
+ let(:session) do
1391
+ authorized_client.start_session
1392
+ end
1393
+
1394
+ let(:operation) do
1395
+ authorized_collection.delete_one({}, session: session)
1396
+ end
1397
+
1398
+ let(:failed_operation) do
1399
+ authorized_collection.delete_one({ '$._id' => 1}, session: session)
1400
+ end
1401
+
1402
+ let(:client) do
1403
+ authorized_client
1404
+ end
1405
+
1406
+ it_behaves_like 'an operation using a session'
1407
+ it_behaves_like 'a failed operation using a session'
1408
+ end
1409
+
1410
+ context 'when unacknowledged writes is used' do
1411
+
1412
+ let(:collection_with_unacknowledged_write_concern) do
1413
+ authorized_collection.with(write: { w: 0 })
1414
+ end
1415
+
1416
+ let(:operation) do
1417
+ collection_with_unacknowledged_write_concern.delete_one({}, session: session)
1418
+ end
1419
+
1420
+ it_behaves_like 'an explicit session with an unacknowledged write'
1421
+ end
1422
+
1423
+ context 'when unacknowledged writes is used with an implicit session' do
1424
+
1425
+ let(:collection_with_unacknowledged_write_concern) do
1426
+ client.with(write: { w: 0 })[TEST_COLL]
1427
+ end
1428
+
1429
+ let(:operation) do
1430
+ collection_with_unacknowledged_write_concern.delete_one
1431
+ end
1432
+
1433
+ it_behaves_like 'an implicit session with an unacknowledged write'
1434
+ end
1435
+
1436
+ context 'when a collation is provided' do
1437
+
1438
+ let(:selector) do
1439
+ { name: 'BANG' }
1440
+ end
1441
+
1442
+ let(:result) do
1443
+ authorized_collection.delete_one(selector, options)
1444
+ end
1445
+
1446
+ before do
1447
+ authorized_collection.insert_one(name: 'bang')
1448
+ end
1449
+
1450
+ let(:options) do
1451
+ { collation: { locale: 'en_US', strength: 2 } }
1452
+ end
1453
+
1454
+ context 'when the server selected supports collations' do
1455
+ min_server_fcv '3.4'
1456
+
1457
+ it 'applies the collation' do
1458
+ expect(result.written_count).to eq(1)
1459
+ expect(authorized_collection.find(name: 'bang').count).to eq(0)
1460
+ end
1461
+
1462
+ context 'when unacknowledged writes is used' do
1463
+
1464
+ let(:collection_with_unacknowledged_write_concern) do
1465
+ authorized_collection.with(write: { w: 0 })
1466
+ end
1467
+
1468
+ let(:result) do
1469
+ collection_with_unacknowledged_write_concern.delete_one(selector, options)
1470
+ end
1471
+
1472
+ it 'raises an exception' do
1473
+ expect {
1474
+ result
1475
+ }.to raise_exception(Mongo::Error::UnsupportedCollation)
1476
+ end
1477
+
1478
+ context 'when a String key is used' do
1479
+
1480
+ let(:options) do
1481
+ { 'collation' => { locale: 'en_US', strength: 2 } }
1482
+ end
1483
+
1484
+ it 'raises an exception' do
1485
+ expect {
1486
+ result
1487
+ }.to raise_exception(Mongo::Error::UnsupportedCollation)
1488
+ end
1489
+ end
1490
+ end
1491
+ end
1492
+
1493
+ context 'when the server selected does not support collations' do
1494
+ max_server_version '3.2'
1495
+
1496
+ it 'raises an exception' do
1497
+ expect {
1498
+ result
1499
+ }.to raise_exception(Mongo::Error::UnsupportedCollation)
1500
+ end
1501
+
1502
+ context 'when a String key is used' do
1503
+
1504
+ let(:options) do
1505
+ { 'collation' => { locale: 'en_US', strength: 2 } }
1506
+ end
1507
+
1508
+ it 'raises an exception' do
1509
+ expect {
1510
+ result
1511
+ }.to raise_exception(Mongo::Error::UnsupportedCollation)
1512
+ end
1513
+ end
1514
+ end
1515
+ end
1516
+
1517
+ context 'when collation is not specified' do
1518
+
1519
+ let(:selector) do
1520
+ { name: 'BANG' }
1521
+ end
1522
+
1523
+ let(:result) do
1524
+ authorized_collection.delete_one(selector)
1525
+ end
1526
+
1527
+ before do
1528
+ authorized_collection.insert_one(name: 'bang')
1529
+ end
1530
+
1531
+ it 'does not apply the collation' do
1532
+ expect(result.written_count).to eq(0)
1533
+ expect(authorized_collection.find(name: 'bang').count).to eq(1)
1534
+ end
1535
+ end
1536
+
1537
+ context 'when various options passed in' do
1538
+ # w: 2 requires a replica set
1539
+ require_topology :replica_set
1540
+
1541
+ # https://jira.mongodb.org/browse/RUBY-2306
1542
+ min_server_fcv '3.6'
1543
+
1544
+ before do
1545
+ authorized_collection.insert_many([{ name: 'test1' }, { name: 'test2' }])
1546
+ end
1547
+
1548
+ let(:selector) do
1549
+ {name: 'test2'}
1550
+ end
1551
+
1552
+ let(:session) do
1553
+ authorized_client.start_session
1554
+ end
1555
+
1556
+ let(:events) do
1557
+ subscriber.command_started_events('delete')
1558
+ end
1559
+
1560
+ let(:collection) do
1561
+ authorized_collection.with(write_concern: {w: 2})
1562
+ end
1563
+
1564
+ let!(:command) do
1565
+ Utils.get_command_event(authorized_client, 'delete') do |client|
1566
+ collection.delete_one(selector, session: session, write_concern: {w: 1},
1567
+ bypass_document_validation: true)
1568
+ end.command
1569
+ end
1570
+
1571
+ it 'deletes one successfully with correct options sent to server' do
1572
+ expect(events.length).to eq(1)
1573
+ expect(command[:writeConcern]).to_not be_nil
1574
+ expect(command[:writeConcern][:w]).to eq(1)
1575
+ expect(command[:bypassDocumentValidation]).to eq(true)
1576
+ end
1577
+ end
1578
+ end
1579
+
1580
+ describe '#delete_many' do
1581
+
1582
+ before do
1583
+ authorized_collection.insert_many([{ field: 'test1' }, { field: 'test2' }])
1584
+ end
1585
+
1586
+ context 'when a selector was provided' do
1587
+
1588
+ let(:selector) do
1589
+ { field: 'test1' }
1590
+ end
1591
+
1592
+ it 'deletes the matching documents in the collection' do
1593
+ expect(authorized_collection.delete_many(selector).deleted_count).to eq(1)
1594
+ end
1595
+ end
1596
+
1597
+ context 'when no selector was provided' do
1598
+
1599
+ it 'deletes all the documents in the collection' do
1600
+ expect(authorized_collection.delete_many.deleted_count).to eq(2)
1601
+ end
1602
+ end
1603
+
1604
+ context 'when the deletes fail' do
1605
+ require_topology :single
1606
+
1607
+ let(:result) do
1608
+ collection_invalid_write_concern.delete_many
1609
+ end
1610
+
1611
+ it 'raises an OperationFailure' do
1612
+ expect {
1613
+ result
1614
+ }.to raise_exception(Mongo::Error::OperationFailure)
1615
+ end
1616
+ end
1617
+
1618
+ context 'when a session is provided' do
1619
+
1620
+ let(:session) do
1621
+ authorized_client.start_session
1622
+ end
1623
+
1624
+ let(:operation) do
1625
+ authorized_collection.delete_many({}, session: session)
1626
+ end
1627
+
1628
+ let(:failed_operation) do
1629
+ authorized_collection.delete_many({ '$._id' => 1}, session: session)
1630
+ end
1631
+
1632
+ let(:client) do
1633
+ authorized_client
1634
+ end
1635
+
1636
+ it_behaves_like 'an operation using a session'
1637
+ it_behaves_like 'a failed operation using a session'
1638
+ end
1639
+
1640
+ context 'when unacknowledged writes are used with an explicit session' do
1641
+
1642
+ let(:collection_with_unacknowledged_write_concern) do
1643
+ authorized_collection.with(write: { w: 0 })
1644
+ end
1645
+
1646
+ let(:operation) do
1647
+ collection_with_unacknowledged_write_concern.delete_many({ '$._id' => 1}, session: session)
1648
+ end
1649
+
1650
+ it_behaves_like 'an explicit session with an unacknowledged write'
1651
+ end
1652
+
1653
+ context 'when unacknowledged writes are used with an implicit session' do
1654
+
1655
+ let(:collection_with_unacknowledged_write_concern) do
1656
+ client.with(write: { w: 0 })[TEST_COLL]
1657
+ end
1658
+
1659
+ let(:operation) do
1660
+ collection_with_unacknowledged_write_concern.delete_many({ '$._id' => 1 })
1661
+ end
1662
+
1663
+ it_behaves_like 'an implicit session with an unacknowledged write'
1664
+ end
1665
+
1666
+ context 'when a collation is specified' do
1667
+
1668
+ let(:selector) do
1669
+ { name: 'BANG' }
1670
+ end
1671
+
1672
+ let(:result) do
1673
+ authorized_collection.delete_many(selector, options)
1674
+ end
1675
+
1676
+ before do
1677
+ authorized_collection.insert_one(name: 'bang')
1678
+ authorized_collection.insert_one(name: 'bang')
1679
+ end
1680
+
1681
+ let(:options) do
1682
+ { collation: { locale: 'en_US', strength: 2 } }
1683
+ end
1684
+
1685
+ context 'when the server selected supports collations' do
1686
+ min_server_fcv '3.4'
1687
+
1688
+ it 'applies the collation' do
1689
+ expect(result.written_count).to eq(2)
1690
+ expect(authorized_collection.find(name: 'bang').count).to eq(0)
1691
+ end
1692
+
1693
+ context 'when unacknowledged writes is used' do
1694
+
1695
+ let(:collection_with_unacknowledged_write_concern) do
1696
+ authorized_collection.with(write: { w: 0 })
1697
+ end
1698
+
1699
+ let(:result) do
1700
+ collection_with_unacknowledged_write_concern.delete_many(selector, options)
1701
+ end
1702
+
1703
+ it 'raises an exception' do
1704
+ expect {
1705
+ result
1706
+ }.to raise_exception(Mongo::Error::UnsupportedCollation)
1707
+ end
1708
+
1709
+ context 'when a String key is used' do
1710
+
1711
+ let(:options) do
1712
+ { 'collation' => { locale: 'en_US', strength: 2 } }
1713
+ end
1714
+
1715
+ it 'raises an exception' do
1716
+ expect {
1717
+ result
1718
+ }.to raise_exception(Mongo::Error::UnsupportedCollation)
1719
+ end
1720
+ end
1721
+ end
1722
+ end
1723
+
1724
+ context 'when the server selected does not support collations' do
1725
+ max_server_version '3.2'
1726
+
1727
+ it 'raises an exception' do
1728
+ expect {
1729
+ result
1730
+ }.to raise_exception(Mongo::Error::UnsupportedCollation)
1731
+ end
1732
+
1733
+ context 'when a String key is used' do
1734
+
1735
+ let(:options) do
1736
+ { 'collation' => { locale: 'en_US', strength: 2 } }
1737
+ end
1738
+
1739
+ it 'raises an exception' do
1740
+ expect {
1741
+ result
1742
+ }.to raise_exception(Mongo::Error::UnsupportedCollation)
1743
+ end
1744
+ end
1745
+ end
1746
+ end
1747
+
1748
+ context 'when a collation is not specified' do
1749
+
1750
+ let(:selector) do
1751
+ { name: 'BANG' }
1752
+ end
1753
+
1754
+ let(:result) do
1755
+ authorized_collection.delete_many(selector)
1756
+ end
1757
+
1758
+ before do
1759
+ authorized_collection.insert_one(name: 'bang')
1760
+ authorized_collection.insert_one(name: 'bang')
1761
+ end
1762
+
1763
+ it 'does not apply the collation' do
1764
+ expect(result.written_count).to eq(0)
1765
+ expect(authorized_collection.find(name: 'bang').count).to eq(2)
1766
+ end
1767
+ end
1768
+
1769
+ context 'when various options passed in' do
1770
+ # w: 2 requires a replica set
1771
+ require_topology :replica_set
1772
+
1773
+ # https://jira.mongodb.org/browse/RUBY-2306
1774
+ min_server_fcv '3.6'
1775
+
1776
+ before do
1777
+ collection.insert_many([{ name: 'test1' }, { name: 'test2' }, { name: 'test3'}])
1778
+ end
1779
+
1780
+ let(:selector) do
1781
+ {name: 'test1'}
1782
+ end
1783
+
1784
+ let(:session) do
1785
+ authorized_client.start_session
1786
+ end
1787
+
1788
+ let(:events) do
1789
+ subscriber.command_started_events('delete')
1790
+ end
1791
+
1792
+ let(:collection) do
1793
+ authorized_collection.with(write_concern: {w: 1})
1794
+ end
1795
+
1796
+ let!(:command) do
1797
+ Utils.get_command_event(authorized_client, 'delete') do |client|
1798
+ collection.delete_many(selector, session: session, write_concern: {w: 2},
1799
+ bypass_document_validation: true)
1800
+ end.command
1801
+ end
1802
+
1803
+ it 'deletes many successfully with correct options sent to server' do
1804
+ expect(events.length).to eq(1)
1805
+ expect(command[:writeConcern]).to_not be_nil
1806
+ expect(command[:writeConcern][:w]).to eq(2)
1807
+ expect(command[:bypassDocumentValidation]).to be(true)
1808
+ end
1809
+ end
1810
+ end
1811
+
1812
+ describe '#parallel_scan' do
1813
+ max_server_version '4.0'
1814
+ require_topology :single, :replica_set
1815
+
1816
+ let(:documents) do
1817
+ (1..200).map do |i|
1818
+ { name: "testing-scan-#{i}" }
1819
+ end
1820
+ end
1821
+
1822
+ before do
1823
+ authorized_collection.insert_many(documents)
1824
+ end
1825
+
1826
+ let(:cursors) do
1827
+ authorized_collection.parallel_scan(2)
1828
+ end
1829
+
1830
+ it 'returns an array of cursors' do
1831
+ cursors.each do |cursor|
1832
+ expect(cursor.class).to be(Mongo::Cursor)
1833
+ end
1834
+ end
1835
+
1836
+ it 'returns the correct number of documents' do
1837
+ expect(
1838
+ cursors.reduce(0) { |total, cursor| total + cursor.to_a.size }
1839
+ ).to eq(200)
1840
+ end
1841
+
1842
+ context 'when a session is provided' do
1843
+ require_wired_tiger
1844
+
1845
+ let(:cursors) do
1846
+ authorized_collection.parallel_scan(2, session: session)
1847
+ end
1848
+
1849
+ let(:operation) do
1850
+ cursors.reduce(0) { |total, cursor| total + cursor.to_a.size }
1851
+ end
1852
+
1853
+ let(:failed_operation) do
1854
+ authorized_collection.parallel_scan(-2, session: session)
1855
+ end
1856
+
1857
+ let(:client) do
1858
+ authorized_client
1859
+ end
1860
+
1861
+ it_behaves_like 'an operation using a session'
1862
+ it_behaves_like 'a failed operation using a session'
1863
+ end
1864
+
1865
+ context 'when a session is not provided' do
1866
+ let(:collection) { client['test'] }
1867
+
1868
+ let(:cursors) do
1869
+ collection.parallel_scan(2)
1870
+ end
1871
+
1872
+ let(:operation) do
1873
+ cursors.reduce(0) { |total, cursor| total + cursor.to_a.size }
1874
+ end
1875
+
1876
+ let(:failed_operation) do
1877
+ collection.parallel_scan(-2)
1878
+ end
1879
+
1880
+ let(:command) do
1881
+ operation
1882
+ event = subscriber.started_events.find { |cmd| cmd.command_name == 'parallelCollectionScan' }
1883
+ expect(event).not_to be_nil
1884
+ event.command
1885
+ end
1886
+
1887
+ it_behaves_like 'an operation not using a session'
1888
+ it_behaves_like 'a failed operation not using a session'
1889
+ end
1890
+
1891
+ context 'when a session supporting causal consistency is used' do
1892
+ require_wired_tiger
1893
+
1894
+ let(:cursors) do
1895
+ collection.parallel_scan(2, session: session)
1896
+ end
1897
+
1898
+ let(:operation) do
1899
+ cursors.reduce(0) { |total, cursor| total + cursor.to_a.size }
1900
+ end
1901
+
1902
+ let(:command) do
1903
+ operation
1904
+ event = subscriber.started_events.find { |cmd| cmd.command_name == 'parallelCollectionScan' }
1905
+ expect(event).not_to be_nil
1906
+ event.command
1907
+ end
1908
+
1909
+ it_behaves_like 'an operation supporting causally consistent reads'
1910
+ end
1911
+
1912
+ context 'when a read concern is provided' do
1913
+ require_wired_tiger
1914
+ min_server_fcv '3.2'
1915
+
1916
+ let(:result) do
1917
+ authorized_collection.with(options).parallel_scan(2)
1918
+ end
1919
+
1920
+ context 'when the read concern is valid' do
1921
+
1922
+ let(:options) do
1923
+ { read_concern: { level: 'local' }}
1924
+ end
1925
+
1926
+ it 'sends the read concern' do
1927
+ expect { result }.to_not raise_error
1928
+ end
1929
+ end
1930
+
1931
+ context 'when the read concern is not valid' do
1932
+
1933
+ let(:options) do
1934
+ { read_concern: { level: 'idontknow' }}
1935
+ end
1936
+
1937
+ it 'raises an exception' do
1938
+ expect {
1939
+ result
1940
+ }.to raise_error(Mongo::Error::OperationFailure)
1941
+ end
1942
+ end
1943
+ end
1944
+
1945
+ context 'when the collection has a read preference' do
1946
+ require_topology :single, :replica_set
1947
+
1948
+ before do
1949
+ allow(collection.client.cluster).to receive(:single?).and_return(false)
1950
+ end
1951
+
1952
+ let(:client) do
1953
+ authorized_client.with(server_selection_timeout: 0.2)
1954
+ end
1955
+
1956
+ let(:collection) do
1957
+ client[authorized_collection.name,
1958
+ read: { :mode => :secondary, :tag_sets => [{ 'non' => 'existent' }] }]
1959
+ end
1960
+
1961
+ let(:result) do
1962
+ collection.parallel_scan(2)
1963
+ end
1964
+
1965
+ it 'uses that read preference' do
1966
+ expect {
1967
+ result
1968
+ }.to raise_exception(Mongo::Error::NoServerAvailable)
1969
+ end
1970
+ end
1971
+
1972
+ context 'when a max time ms value is provided' do
1973
+ require_topology :single, :replica_set
1974
+
1975
+ let(:result) do
1976
+ authorized_collection.parallel_scan(2, options)
1977
+ end
1978
+
1979
+ context 'when the read concern is valid' do
1980
+
1981
+ let(:options) do
1982
+ { max_time_ms: 5 }
1983
+ end
1984
+
1985
+ it 'sends the max time ms value' do
1986
+ expect { result }.to_not raise_error
1987
+ end
1988
+ end
1989
+
1990
+ context 'when the max time ms is not valid' do
1991
+
1992
+ let(:options) do
1993
+ { max_time_ms: 0.1 }
1994
+ end
1995
+
1996
+ it 'raises an exception' do
1997
+ expect {
1998
+ result
1999
+ }.to raise_error(Mongo::Error::OperationFailure)
2000
+ end
2001
+ end
2002
+ end
2003
+ end
2004
+
2005
+ describe '#replace_one' do
2006
+
2007
+ let(:selector) do
2008
+ { field: 'test1' }
2009
+ end
2010
+
2011
+ context 'when a selector was provided' do
2012
+
2013
+ before do
2014
+ authorized_collection.insert_many([{ field: 'test1' }, { field: 'test1' }])
2015
+ end
2016
+
2017
+ let!(:response) do
2018
+ authorized_collection.replace_one(selector, { field: 'testing' })
2019
+ end
2020
+
2021
+ let(:updated) do
2022
+ authorized_collection.find(field: 'testing').first
2023
+ end
2024
+
2025
+ it 'updates the first matching document in the collection' do
2026
+ expect(response.modified_count).to eq(1)
2027
+ end
2028
+
2029
+ it 'updates the documents in the collection' do
2030
+ expect(updated[:field]).to eq('testing')
2031
+ end
2032
+ end
2033
+
2034
+ context 'when upsert is false' do
2035
+
2036
+ let!(:response) do
2037
+ authorized_collection.replace_one(selector, { field: 'test1' }, upsert: false)
2038
+ end
2039
+
2040
+ let(:updated) do
2041
+ authorized_collection.find(field: 'test1').to_a
2042
+ end
2043
+
2044
+ it 'reports that no documents were written' do
2045
+ expect(response.modified_count).to eq(0)
2046
+ end
2047
+
2048
+ it 'does not insert the document' do
2049
+ expect(updated).to be_empty
2050
+ end
2051
+ end
2052
+
2053
+ context 'when upsert is true' do
2054
+
2055
+ let!(:response) do
2056
+ authorized_collection.replace_one(selector, { field: 'test1' }, upsert: true)
2057
+ end
2058
+
2059
+ let(:updated) do
2060
+ authorized_collection.find(field: 'test1').first
2061
+ end
2062
+
2063
+ it 'reports that a document was written' do
2064
+ expect(response.written_count).to eq(1)
2065
+ end
2066
+
2067
+ it 'inserts the document' do
2068
+ expect(updated[:field]).to eq('test1')
2069
+ end
2070
+ end
2071
+
2072
+ context 'when upsert is not specified' do
2073
+
2074
+ let!(:response) do
2075
+ authorized_collection.replace_one(selector, { field: 'test1' })
2076
+ end
2077
+
2078
+ let(:updated) do
2079
+ authorized_collection.find(field: 'test1').to_a
2080
+ end
2081
+
2082
+ it 'reports that no documents were written' do
2083
+ expect(response.modified_count).to eq(0)
2084
+ end
2085
+
2086
+ it 'does not insert the document' do
2087
+ expect(updated).to be_empty
2088
+ end
2089
+ end
2090
+
2091
+ context 'when the replace fails' do
2092
+
2093
+ let(:result) do
2094
+ authorized_collection.replace_one(selector, { '$s' => 'test1' })
2095
+ end
2096
+
2097
+ it 'raises an OperationFailure' do
2098
+ expect {
2099
+ result
2100
+ }.to raise_exception(Mongo::Error::OperationFailure)
2101
+ end
2102
+ end
2103
+
2104
+ context 'when collection has a validator' do
2105
+ min_server_fcv '3.2'
2106
+
2107
+ around(:each) do |spec|
2108
+ collection_with_validator.drop
2109
+ authorized_client[:validating,
2110
+ :validator => { :a => { '$exists' => true } }].tap do |c|
2111
+ c.create
2112
+ end
2113
+ spec.run
2114
+ collection_with_validator.drop
2115
+ end
2116
+
2117
+ before do
2118
+ collection_with_validator.insert_one({ a: 1 })
2119
+ end
2120
+
2121
+ context 'when the document is valid' do
2122
+
2123
+ let(:result) do
2124
+ collection_with_validator.replace_one({ a: 1 }, { a: 5 })
2125
+ end
2126
+
2127
+ it 'replaces successfully' do
2128
+ expect(result.modified_count).to eq(1)
2129
+ end
2130
+ end
2131
+
2132
+ context 'when the document is invalid' do
2133
+
2134
+ context 'when bypass_document_validation is not set' do
2135
+
2136
+ let(:result2) do
2137
+ collection_with_validator.replace_one({ a: 1 }, { x: 5 })
2138
+ end
2139
+
2140
+ it 'raises OperationFailure' do
2141
+ expect {
2142
+ result2
2143
+ }.to raise_exception(Mongo::Error::OperationFailure)
2144
+ end
2145
+ end
2146
+
2147
+ context 'when bypass_document_validation is true' do
2148
+
2149
+ let(:result3) do
2150
+ collection_with_validator.replace_one(
2151
+ { a: 1 }, { x: 1 }, :bypass_document_validation => true)
2152
+ end
2153
+
2154
+ it 'replaces successfully' do
2155
+ expect(result3.written_count).to eq(1)
2156
+ end
2157
+ end
2158
+ end
2159
+ end
2160
+
2161
+ context 'when a collation is specified' do
2162
+
2163
+ let(:selector) do
2164
+ { name: 'BANG' }
2165
+ end
2166
+
2167
+ let(:result) do
2168
+ authorized_collection.replace_one(selector, { name: 'doink' }, options)
2169
+ end
2170
+
2171
+ before do
2172
+ authorized_collection.insert_one(name: 'bang')
2173
+ end
2174
+
2175
+ let(:options) do
2176
+ { collation: { locale: 'en_US', strength: 2 } }
2177
+ end
2178
+
2179
+ context 'when the server selected supports collations' do
2180
+ min_server_fcv '3.4'
2181
+
2182
+ it 'applies the collation' do
2183
+ expect(result.written_count).to eq(1)
2184
+ expect(authorized_collection.find(name: 'doink').count).to eq(1)
2185
+ end
2186
+
2187
+ context 'when unacknowledged writes is used' do
2188
+
2189
+ let(:collection_with_unacknowledged_write_concern) do
2190
+ authorized_collection.with(write: { w: 0 })
2191
+ end
2192
+
2193
+ let(:result) do
2194
+ collection_with_unacknowledged_write_concern.replace_one(selector, { name: 'doink' }, options)
2195
+ end
2196
+
2197
+ it 'raises an exception' do
2198
+ expect {
2199
+ result
2200
+ }.to raise_exception(Mongo::Error::UnsupportedCollation)
2201
+ end
2202
+
2203
+ context 'when a String key is used' do
2204
+
2205
+ let(:options) do
2206
+ { 'collation' => { locale: 'en_US', strength: 2 } }
2207
+ end
2208
+
2209
+ it 'raises an exception' do
2210
+ expect {
2211
+ result
2212
+ }.to raise_exception(Mongo::Error::UnsupportedCollation)
2213
+ end
2214
+ end
2215
+ end
2216
+ end
2217
+
2218
+ context 'when the server selected does not support collations' do
2219
+ max_server_version '3.2'
2220
+
2221
+ it 'raises an exception' do
2222
+ expect {
2223
+ result
2224
+ }.to raise_exception(Mongo::Error::UnsupportedCollation)
2225
+ end
2226
+
2227
+ context 'when a String key is used' do
2228
+
2229
+ let(:options) do
2230
+ { 'collation' => { locale: 'en_US', strength: 2 } }
2231
+ end
2232
+
2233
+ it 'raises an exception' do
2234
+ expect {
2235
+ result
2236
+ }.to raise_exception(Mongo::Error::UnsupportedCollation)
2237
+ end
2238
+ end
2239
+ end
2240
+ end
2241
+
2242
+ context 'when a collation is not specified' do
2243
+
2244
+ let(:selector) do
2245
+ { name: 'BANG' }
2246
+ end
2247
+
2248
+ let(:result) do
2249
+ authorized_collection.replace_one(selector, { name: 'doink' })
2250
+ end
2251
+
2252
+ before do
2253
+ authorized_collection.insert_one(name: 'bang')
2254
+ end
2255
+
2256
+ it 'does not apply the collation' do
2257
+ expect(result.written_count).to eq(0)
2258
+ expect(authorized_collection.find(name: 'bang').count).to eq(1)
2259
+ end
2260
+ end
2261
+
2262
+ context 'when a session is provided' do
2263
+
2264
+ let(:selector) do
2265
+ { name: 'BANG' }
2266
+ end
2267
+
2268
+ before do
2269
+ authorized_collection.insert_one(name: 'bang')
2270
+ end
2271
+
2272
+ let(:session) do
2273
+ authorized_client.start_session
2274
+ end
2275
+
2276
+ let(:operation) do
2277
+ authorized_collection.replace_one(selector, { name: 'doink' }, session: session)
2278
+ end
2279
+
2280
+ let(:failed_operation) do
2281
+ authorized_collection.replace_one({ '$._id' => 1 }, { name: 'doink' }, session: session)
2282
+ end
2283
+
2284
+ let(:client) do
2285
+ authorized_client
2286
+ end
2287
+
2288
+ it_behaves_like 'an operation using a session'
2289
+ it_behaves_like 'a failed operation using a session'
2290
+ end
2291
+
2292
+ context 'when unacknowledged writes is used with an explicit session' do
2293
+
2294
+ let(:collection_with_unacknowledged_write_concern) do
2295
+ authorized_collection.with(write: { w: 0 })
2296
+ end
2297
+
2298
+ let(:operation) do
2299
+ collection_with_unacknowledged_write_concern.replace_one({ a: 1 }, { x: 5 }, session: session)
2300
+ end
2301
+
2302
+ it_behaves_like 'an explicit session with an unacknowledged write'
2303
+ end
2304
+
2305
+ context 'when unacknowledged writes is used with an implicit session' do
2306
+
2307
+ let(:collection_with_unacknowledged_write_concern) do
2308
+ client.with(write: { w: 0 })[TEST_COLL]
2309
+ end
2310
+
2311
+ let(:operation) do
2312
+ collection_with_unacknowledged_write_concern.replace_one({ a: 1 }, { x: 5 })
2313
+ end
2314
+
2315
+ it_behaves_like 'an implicit session with an unacknowledged write'
2316
+ end
2317
+
2318
+ context 'when various options passed in' do
2319
+ # w: 2 requires a replica set
2320
+ require_topology :replica_set
2321
+
2322
+ # https://jira.mongodb.org/browse/RUBY-2306
2323
+ min_server_fcv '3.6'
2324
+
2325
+ before do
2326
+ authorized_collection.insert_one({field: 'test1'})
2327
+ end
2328
+
2329
+ let(:session) do
2330
+ authorized_client.start_session
2331
+ end
2332
+
2333
+ let(:events) do
2334
+ subscriber.command_started_events('update')
2335
+ end
2336
+
2337
+ let(:collection) do
2338
+ authorized_collection.with(write_concern: {w: 3})
2339
+ end
2340
+
2341
+ let(:updated) do
2342
+ collection.find(field: 'test4').first
2343
+ end
2344
+
2345
+ let!(:command) do
2346
+ Utils.get_command_event(authorized_client, 'update') do |client|
2347
+ collection.replace_one(selector, { field: 'test4'},
2348
+ session: session, :return_document => :after, write_concern: {w: 2},
2349
+ upsert: true, bypass_document_validation: true)
2350
+ end.command
2351
+ end
2352
+
2353
+ it 'replaced one successfully with correct options sent to server' do
2354
+ expect(updated[:field]).to eq('test4')
2355
+ expect(events.length).to eq(1)
2356
+ expect(command[:writeConcern]).to_not be_nil
2357
+ expect(command[:writeConcern][:w]).to eq(2)
2358
+ expect(command[:bypassDocumentValidation]).to be(true)
2359
+ expect(command[:updates][0][:upsert]).to be(true)
2360
+ end
2361
+ end
2362
+ end
2363
+
2364
+ describe '#update_many' do
2365
+
2366
+ let(:selector) do
2367
+ { field: 'test' }
2368
+ end
2369
+
2370
+ context 'when a selector was provided' do
2371
+
2372
+ before do
2373
+ authorized_collection.insert_many([{ field: 'test' }, { field: 'test' }])
2374
+ end
2375
+
2376
+ let!(:response) do
2377
+ authorized_collection.update_many(selector, '$set'=> { field: 'testing' })
2378
+ end
2379
+
2380
+ let(:updated) do
2381
+ authorized_collection.find(field: 'testing').to_a.last
2382
+ end
2383
+
2384
+ it 'returns the number updated' do
2385
+ expect(response.modified_count).to eq(2)
2386
+ end
2387
+
2388
+ it 'updates the documents in the collection' do
2389
+ expect(updated[:field]).to eq('testing')
2390
+ end
2391
+ end
2392
+
2393
+ context 'when upsert is false' do
2394
+
2395
+ let(:response) do
2396
+ authorized_collection.update_many(selector, { '$set'=> { field: 'testing' } },
2397
+ upsert: false)
2398
+ end
2399
+
2400
+ let(:updated) do
2401
+ authorized_collection.find.to_a
2402
+ end
2403
+
2404
+ it 'reports that no documents were updated' do
2405
+ expect(response.modified_count).to eq(0)
2406
+ end
2407
+
2408
+ it 'updates no documents in the collection' do
2409
+ expect(updated).to be_empty
2410
+ end
2411
+ end
2412
+
2413
+ context 'when upsert is true' do
2414
+
2415
+ let!(:response) do
2416
+ authorized_collection.update_many(selector, { '$set'=> { field: 'testing' } },
2417
+ upsert: true)
2418
+ end
2419
+
2420
+ let(:updated) do
2421
+ authorized_collection.find.to_a.last
2422
+ end
2423
+
2424
+ it 'reports that a document was written' do
2425
+ expect(response.written_count).to eq(1)
2426
+ end
2427
+
2428
+ it 'inserts a document into the collection' do
2429
+ expect(updated[:field]).to eq('testing')
2430
+ end
2431
+ end
2432
+
2433
+ context 'when upsert is not specified' do
2434
+
2435
+ let(:response) do
2436
+ authorized_collection.update_many(selector, { '$set'=> { field: 'testing' } })
2437
+ end
2438
+
2439
+ let(:updated) do
2440
+ authorized_collection.find.to_a
2441
+ end
2442
+
2443
+ it 'reports that no documents were updated' do
2444
+ expect(response.modified_count).to eq(0)
2445
+ end
2446
+
2447
+ it 'updates no documents in the collection' do
2448
+ expect(updated).to be_empty
2449
+ end
2450
+ end
2451
+
2452
+ context 'when arrayFilters is provided' do
2453
+
2454
+ let(:selector) do
2455
+ { '$or' => [{ _id: 0 }, { _id: 1 }]}
2456
+ end
2457
+
2458
+ context 'when the server supports arrayFilters' do
2459
+ min_server_fcv '3.6'
2460
+
2461
+ before do
2462
+ authorized_collection.insert_many([{
2463
+ _id: 0, x: [
2464
+ { y: 1 },
2465
+ { y: 2 },
2466
+ { y: 3 }
2467
+ ]
2468
+ },
2469
+ {
2470
+ _id: 1,
2471
+ x: [
2472
+ { y: 3 },
2473
+ { y: 2 },
2474
+ { y: 1 }
2475
+ ]
2476
+ }])
2477
+ end
2478
+
2479
+ let(:result) do
2480
+ authorized_collection.update_many(selector,
2481
+ { '$set' => { 'x.$[i].y' => 5 } },
2482
+ options)
2483
+ end
2484
+
2485
+ context 'when a Symbol key is used' do
2486
+
2487
+ let(:options) do
2488
+ { array_filters: [{ 'i.y' => 3 }] }
2489
+ end
2490
+
2491
+ it 'applies the arrayFilters' do
2492
+ expect(result.matched_count).to eq(2)
2493
+ expect(result.modified_count).to eq(2)
2494
+
2495
+ docs = authorized_collection.find(selector, sort: { _id: 1 }).to_a
2496
+ expect(docs[0]['x']).to eq ([{ 'y' => 1 }, { 'y' => 2 }, { 'y' => 5 }])
2497
+ expect(docs[1]['x']).to eq ([{ 'y' => 5 }, { 'y' => 2 }, { 'y' => 1 }])
2498
+ end
2499
+ end
2500
+
2501
+ context 'when a String key is used' do
2502
+ let(:options) do
2503
+ { 'array_filters' => [{ 'i.y' => 3 }] }
2504
+ end
2505
+
2506
+ it 'applies the arrayFilters' do
2507
+ expect(result.matched_count).to eq(2)
2508
+ expect(result.modified_count).to eq(2)
2509
+
2510
+ docs = authorized_collection.find({}, sort: { _id: 1 }).to_a
2511
+ expect(docs[0]['x']).to eq ([{ 'y' => 1 }, { 'y' => 2 }, { 'y' => 5 }])
2512
+ expect(docs[1]['x']).to eq ([{ 'y' => 5 }, { 'y' => 2 }, { 'y' => 1 }])
2513
+ end
2514
+ end
2515
+ end
2516
+
2517
+ context 'when the server does not support arrayFilters' do
2518
+ max_server_version '3.4'
2519
+
2520
+ let(:result) do
2521
+ authorized_collection.update_many(selector,
2522
+ { '$set' => { 'x.$[i].y' => 5 } },
2523
+ options)
2524
+ end
2525
+
2526
+ context 'when a Symbol key is used' do
2527
+
2528
+ let(:options) do
2529
+ { array_filters: [{ 'i.y' => 3 }] }
2530
+ end
2531
+
2532
+ it 'raises an exception' do
2533
+ expect {
2534
+ result
2535
+ }.to raise_exception(Mongo::Error::UnsupportedArrayFilters)
2536
+ end
2537
+ end
2538
+
2539
+ context 'when a String key is used' do
2540
+
2541
+ let(:options) do
2542
+ { 'array_filters' => [{ 'i.y' => 3 }] }
2543
+ end
2544
+
2545
+ it 'raises an exception' do
2546
+ expect {
2547
+ result
2548
+ }.to raise_exception(Mongo::Error::UnsupportedArrayFilters)
2549
+ end
2550
+ end
2551
+ end
2552
+ end
2553
+
2554
+ context 'when the updates fail' do
2555
+
2556
+ let(:result) do
2557
+ authorized_collection.update_many(selector, { '$s'=> { field: 'testing' } })
2558
+ end
2559
+
2560
+ it 'raises an OperationFailure' do
2561
+ expect {
2562
+ result
2563
+ }.to raise_exception(Mongo::Error::OperationFailure)
2564
+ end
2565
+ end
2566
+
2567
+ context 'when collection has a validator' do
2568
+ min_server_fcv '3.2'
2569
+
2570
+ around(:each) do |spec|
2571
+ authorized_client[:validating,
2572
+ :validator => { :a => { '$exists' => true } }].tap do |c|
2573
+ c.create
2574
+ end
2575
+ spec.run
2576
+ collection_with_validator.drop
2577
+ end
2578
+
2579
+ before do
2580
+ collection_with_validator.insert_many([{ a: 1 }, { a: 2 }])
2581
+ end
2582
+
2583
+ context 'when the document is valid' do
2584
+
2585
+ let(:result) do
2586
+ collection_with_validator.update_many(
2587
+ { :a => { '$gt' => 0 } }, '$inc' => { :a => 1 } )
2588
+ end
2589
+
2590
+ it 'updates successfully' do
2591
+ expect(result.modified_count).to eq(2)
2592
+ end
2593
+ end
2594
+
2595
+ context 'when the document is invalid' do
2596
+
2597
+ context 'when bypass_document_validation is not set' do
2598
+
2599
+ let(:result2) do
2600
+ collection_with_validator.update_many(
2601
+ { :a => { '$gt' => 0 } }, '$unset' => { :a => '' })
2602
+ end
2603
+
2604
+ it 'raises OperationFailure' do
2605
+ expect {
2606
+ result2
2607
+ }.to raise_exception(Mongo::Error::OperationFailure)
2608
+ end
2609
+ end
2610
+
2611
+ context 'when bypass_document_validation is true' do
2612
+
2613
+ let(:result3) do
2614
+ collection_with_validator.update_many(
2615
+ { :a => { '$gt' => 0 } }, { '$unset' => { :a => '' } },
2616
+ :bypass_document_validation => true)
2617
+ end
2618
+
2619
+ it 'updates successfully' do
2620
+ expect(result3.written_count).to eq(2)
2621
+ end
2622
+ end
2623
+ end
2624
+ end
2625
+
2626
+ context 'when a collation is specified' do
2627
+
2628
+ let(:selector) do
2629
+ { name: 'BANG' }
2630
+ end
2631
+
2632
+ let(:result) do
2633
+ authorized_collection.update_many(selector, { '$set' => { other: 'doink' } }, options)
2634
+ end
2635
+
2636
+ before do
2637
+ authorized_collection.insert_one(name: 'bang')
2638
+ authorized_collection.insert_one(name: 'baNG')
2639
+ end
2640
+
2641
+ let(:options) do
2642
+ { collation: { locale: 'en_US', strength: 2 } }
2643
+ end
2644
+
2645
+ context 'when the server selected supports collations' do
2646
+ min_server_fcv '3.4'
2647
+
2648
+ it 'applies the collation' do
2649
+ expect(result.written_count).to eq(2)
2650
+ expect(authorized_collection.find(other: 'doink').count).to eq(2)
2651
+ end
2652
+
2653
+ context 'when unacknowledged writes is used' do
2654
+
2655
+ let(:collection_with_unacknowledged_write_concern) do
2656
+ authorized_collection.with(write: { w: 0 })
2657
+ end
2658
+
2659
+ let(:result) do
2660
+ collection_with_unacknowledged_write_concern.update_many(selector, { '$set' => { other: 'doink' } }, options)
2661
+ end
2662
+
2663
+ it 'raises an exception' do
2664
+ expect {
2665
+ result
2666
+ }.to raise_exception(Mongo::Error::UnsupportedCollation)
2667
+ end
2668
+
2669
+ context 'when a String key is used' do
2670
+
2671
+ let(:options) do
2672
+ { 'collation' => { locale: 'en_US', strength: 2 } }
2673
+ end
2674
+
2675
+ it 'raises an exception' do
2676
+ expect {
2677
+ result
2678
+ }.to raise_exception(Mongo::Error::UnsupportedCollation)
2679
+ end
2680
+ end
2681
+ end
2682
+ end
2683
+
2684
+ context 'when the server selected does not support collations' do
2685
+ max_server_version '3.2'
2686
+
2687
+ it 'raises an exception' do
2688
+ expect {
2689
+ result
2690
+ }.to raise_exception(Mongo::Error::UnsupportedCollation)
2691
+ end
2692
+
2693
+ context 'when a String key is used' do
2694
+
2695
+ let(:options) do
2696
+ { 'collation' => { locale: 'en_US', strength: 2 } }
2697
+ end
2698
+
2699
+ it 'raises an exception' do
2700
+ expect {
2701
+ result
2702
+ }.to raise_exception(Mongo::Error::UnsupportedCollation)
2703
+ end
2704
+ end
2705
+ end
2706
+ end
2707
+
2708
+ context 'when collation is not specified' do
2709
+
2710
+ let(:selector) do
2711
+ {name: 'BANG'}
2712
+ end
2713
+
2714
+ let(:result) do
2715
+ authorized_collection.update_many(selector, { '$set' => {other: 'doink'} })
2716
+ end
2717
+
2718
+ before do
2719
+ authorized_collection.insert_one(name: 'bang')
2720
+ authorized_collection.insert_one(name: 'baNG')
2721
+ end
2722
+
2723
+ it 'does not apply the collation' do
2724
+ expect(result.written_count).to eq(0)
2725
+ end
2726
+ end
2727
+
2728
+ context 'when a session is provided' do
2729
+
2730
+ let(:selector) do
2731
+ { name: 'BANG' }
2732
+ end
2733
+
2734
+ let(:operation) do
2735
+ authorized_collection.update_many(selector, { '$set' => {other: 'doink'} }, session: session)
2736
+ end
2737
+
2738
+ before do
2739
+ authorized_collection.insert_one(name: 'bang')
2740
+ authorized_collection.insert_one(name: 'baNG')
2741
+ end
2742
+
2743
+ let(:session) do
2744
+ authorized_client.start_session
2745
+ end
2746
+
2747
+ let(:failed_operation) do
2748
+ authorized_collection.update_many({ '$._id' => 1 }, { '$set' => {other: 'doink'} }, session: session)
2749
+ end
2750
+
2751
+ let(:client) do
2752
+ authorized_client
2753
+ end
2754
+
2755
+ it_behaves_like 'an operation using a session'
2756
+ it_behaves_like 'a failed operation using a session'
2757
+ end
2758
+
2759
+ context 'when unacknowledged writes is used with an explicit session' do
2760
+
2761
+ let(:collection_with_unacknowledged_write_concern) do
2762
+ authorized_collection.with(write: { w: 0 })
2763
+ end
2764
+
2765
+ let(:operation) do
2766
+ collection_with_unacknowledged_write_concern.update_many({a: 1}, { '$set' => {x: 1} }, session: session)
2767
+ end
2768
+
2769
+ it_behaves_like 'an explicit session with an unacknowledged write'
2770
+ end
2771
+
2772
+ context 'when unacknowledged writes is used with an implicit session' do
2773
+
2774
+ let(:collection_with_unacknowledged_write_concern) do
2775
+ client.with(write: { w: 0 })[TEST_COLL]
2776
+ end
2777
+
2778
+ let(:operation) do
2779
+ collection_with_unacknowledged_write_concern.update_many({a: 1}, {'$set' => {x: 1}})
2780
+ end
2781
+
2782
+ it_behaves_like 'an implicit session with an unacknowledged write'
2783
+ end
2784
+
2785
+ context 'when various options passed in' do
2786
+ # w: 2 requires a replica set
2787
+ require_topology :replica_set
2788
+
2789
+ # https://jira.mongodb.org/browse/RUBY-2306
2790
+ min_server_fcv '3.6'
2791
+
2792
+ before do
2793
+ collection.insert_many([{ field: 'test' }, { field: 'test2' }], session: session)
2794
+ end
2795
+
2796
+ let(:session) do
2797
+ authorized_client.start_session
2798
+ end
2799
+
2800
+ let(:collection) do
2801
+ authorized_collection.with(write_concern: {w: 1})
2802
+ end
2803
+
2804
+ let(:events) do
2805
+ subscriber.command_started_events('update')
2806
+ end
2807
+
2808
+ let!(:command) do
2809
+ Utils.get_command_event(authorized_client, 'update') do |client|
2810
+ collection.update_many(selector, {'$set'=> { field: 'testing' }}, session: session,
2811
+ write_concern: {w: 2}, bypass_document_validation: true, upsert: true)
2812
+ end.command
2813
+ end
2814
+
2815
+ it 'updates many successfully with correct options sent to server' do
2816
+ expect(events.length).to eq(1)
2817
+ expect(collection.options[:write_concern]).to eq(w: 1)
2818
+ expect(command[:writeConcern][:w]).to eq(2)
2819
+ expect(command[:bypassDocumentValidation]).to be(true)
2820
+ expect(command[:updates][0][:upsert]).to be(true)
2821
+ end
2822
+ end
2823
+ end
2824
+
2825
+ describe '#update_one' do
2826
+
2827
+ let(:selector) do
2828
+ { field: 'test1' }
2829
+ end
2830
+
2831
+ context 'when a selector was provided' do
2832
+
2833
+ before do
2834
+ authorized_collection.insert_many([{ field: 'test1' }, { field: 'test1' }])
2835
+ end
2836
+
2837
+ let!(:response) do
2838
+ authorized_collection.update_one(selector, '$set'=> { field: 'testing' })
2839
+ end
2840
+
2841
+ let(:updated) do
2842
+ authorized_collection.find(field: 'testing').first
2843
+ end
2844
+
2845
+ it 'updates the first matching document in the collection' do
2846
+ expect(response.modified_count).to eq(1)
2847
+ end
2848
+
2849
+ it 'updates the documents in the collection' do
2850
+ expect(updated[:field]).to eq('testing')
2851
+ end
2852
+ end
2853
+
2854
+ context 'when upsert is false' do
2855
+
2856
+ let(:response) do
2857
+ authorized_collection.update_one(selector, { '$set'=> { field: 'testing' } },
2858
+ upsert: false)
2859
+ end
2860
+
2861
+ let(:updated) do
2862
+ authorized_collection.find.to_a
2863
+ end
2864
+
2865
+ it 'reports that no documents were updated' do
2866
+ expect(response.modified_count).to eq(0)
2867
+ end
2868
+
2869
+ it 'updates no documents in the collection' do
2870
+ expect(updated).to be_empty
2871
+ end
2872
+ end
2873
+
2874
+ context 'when upsert is true' do
2875
+
2876
+ let!(:response) do
2877
+ authorized_collection.update_one(selector, { '$set'=> { field: 'testing' } },
2878
+ upsert: true)
2879
+ end
2880
+
2881
+ let(:updated) do
2882
+ authorized_collection.find.first
2883
+ end
2884
+
2885
+ it 'reports that a document was written' do
2886
+ expect(response.written_count).to eq(1)
2887
+ end
2888
+
2889
+ it 'inserts a document into the collection' do
2890
+ expect(updated[:field]).to eq('testing')
2891
+ end
2892
+ end
2893
+
2894
+ context 'when upsert is not specified' do
2895
+
2896
+ let(:response) do
2897
+ authorized_collection.update_one(selector, { '$set'=> { field: 'testing' } })
2898
+ end
2899
+
2900
+ let(:updated) do
2901
+ authorized_collection.find.to_a
2902
+ end
2903
+
2904
+ it 'reports that no documents were updated' do
2905
+ expect(response.modified_count).to eq(0)
2906
+ end
2907
+
2908
+ it 'updates no documents in the collection' do
2909
+ expect(updated).to be_empty
2910
+ end
2911
+ end
2912
+
2913
+ context 'when the update fails' do
2914
+
2915
+ let(:result) do
2916
+ authorized_collection.update_one(selector, { '$s'=> { field: 'testing' } })
2917
+ end
2918
+
2919
+ it 'raises an OperationFailure' do
2920
+ expect {
2921
+ result
2922
+ }.to raise_exception(Mongo::Error::OperationFailure)
2923
+ end
2924
+ end
2925
+
2926
+ context 'when collection has a validator' do
2927
+ min_server_fcv '3.2'
2928
+
2929
+ around(:each) do |spec|
2930
+ authorized_client[:validating,
2931
+ :validator => { :a => { '$exists' => true } }].tap do |c|
2932
+ c.create
2933
+ end
2934
+ spec.run
2935
+ collection_with_validator.drop
2936
+ end
2937
+
2938
+ before do
2939
+ collection_with_validator.insert_one({ a: 1 })
2940
+ end
2941
+
2942
+ context 'when the document is valid' do
2943
+
2944
+ let(:result) do
2945
+ collection_with_validator.update_one(
2946
+ { :a => { '$gt' => 0 } }, '$inc' => { :a => 1 } )
2947
+ end
2948
+
2949
+ it 'updates successfully' do
2950
+ expect(result.modified_count).to eq(1)
2951
+ end
2952
+ end
2953
+
2954
+ context 'when the document is invalid' do
2955
+
2956
+ context 'when bypass_document_validation is not set' do
2957
+
2958
+ let(:result2) do
2959
+ collection_with_validator.update_one(
2960
+ { :a => { '$gt' => 0 } }, '$unset' => { :a => '' })
2961
+ end
2962
+
2963
+ it 'raises OperationFailure' do
2964
+ expect {
2965
+ result2
2966
+ }.to raise_exception(Mongo::Error::OperationFailure)
2967
+ end
2968
+ end
2969
+
2970
+ context 'when bypass_document_validation is true' do
2971
+
2972
+ let(:result3) do
2973
+ collection_with_validator.update_one(
2974
+ { :a => { '$gt' => 0 } }, { '$unset' => { :a => '' } },
2975
+ :bypass_document_validation => true)
2976
+ end
2977
+
2978
+ it 'updates successfully' do
2979
+ expect(result3.written_count).to eq(1)
2980
+ end
2981
+ end
2982
+ end
2983
+ end
2984
+
2985
+ context 'when there is a collation specified' do
2986
+
2987
+ let(:selector) do
2988
+ { name: 'BANG' }
2989
+ end
2990
+
2991
+ let(:result) do
2992
+ authorized_collection.update_one(selector, { '$set' => { other: 'doink' } }, options)
2993
+ end
2994
+
2995
+ before do
2996
+ authorized_collection.insert_one(name: 'bang')
2997
+ end
2998
+
2999
+ let(:options) do
3000
+ { collation: { locale: 'en_US', strength: 2 } }
3001
+ end
3002
+
3003
+ context 'when the server selected supports collations' do
3004
+ min_server_fcv '3.4'
3005
+
3006
+ it 'applies the collation' do
3007
+ expect(result.written_count).to eq(1)
3008
+ expect(authorized_collection.find(other: 'doink').count).to eq(1)
3009
+ end
3010
+
3011
+ context 'when unacknowledged writes is used' do
3012
+
3013
+ let(:collection_with_unacknowledged_write_concern) do
3014
+ authorized_collection.with(write: { w: 0 })
3015
+ end
3016
+
3017
+ let(:result) do
3018
+ collection_with_unacknowledged_write_concern.update_one(selector, { '$set' => { other: 'doink' } }, options)
3019
+ end
3020
+
3021
+ it 'raises an exception' do
3022
+ expect {
3023
+ result
3024
+ }.to raise_exception(Mongo::Error::UnsupportedCollation)
3025
+ end
3026
+
3027
+ context 'when a String key is used' do
3028
+
3029
+ let(:options) do
3030
+ { 'collation' => { locale: 'en_US', strength: 2 } }
3031
+ end
3032
+
3033
+ it 'raises an exception' do
3034
+ expect {
3035
+ result
3036
+ }.to raise_exception(Mongo::Error::UnsupportedCollation)
3037
+ end
3038
+ end
3039
+ end
3040
+ end
3041
+
3042
+ context 'when the server selected does not support collations' do
3043
+ max_server_version '3.2'
3044
+
3045
+ it 'raises an exception' do
3046
+ expect {
3047
+ result
3048
+ }.to raise_exception(Mongo::Error::UnsupportedCollation)
3049
+ end
3050
+
3051
+ context 'when a String key is used' do
3052
+
3053
+ let(:options) do
3054
+ { 'collation' => { locale: 'en_US', strength: 2 } }
3055
+ end
3056
+
3057
+ it 'raises an exception' do
3058
+ expect {
3059
+ result
3060
+ }.to raise_exception(Mongo::Error::UnsupportedCollation)
3061
+ end
3062
+ end
3063
+ end
3064
+ end
3065
+
3066
+ context 'when a collation is not specified' do
3067
+
3068
+ let(:selector) do
3069
+ { name: 'BANG' }
3070
+ end
3071
+
3072
+ let(:result) do
3073
+ authorized_collection.update_one(selector, { '$set' => { other: 'doink' } })
3074
+ end
3075
+
3076
+ before do
3077
+ authorized_collection.insert_one(name: 'bang')
3078
+ end
3079
+
3080
+ it 'does not apply the collation' do
3081
+ expect(result.written_count).to eq(0)
3082
+ end
3083
+ end
3084
+
3085
+
3086
+ context 'when arrayFilters is provided' do
3087
+
3088
+ let(:selector) do
3089
+ { _id: 0}
3090
+ end
3091
+
3092
+ context 'when the server supports arrayFilters' do
3093
+ min_server_fcv '3.6'
3094
+
3095
+ before do
3096
+ authorized_collection.insert_one(_id: 0, x: [{ y: 1 }, { y: 2 }, {y: 3 }])
3097
+ end
3098
+
3099
+ let(:result) do
3100
+ authorized_collection.update_one(selector,
3101
+ { '$set' => { 'x.$[i].y' => 5 } },
3102
+ options)
3103
+ end
3104
+
3105
+ context 'when a Symbol key is used' do
3106
+
3107
+ let(:options) do
3108
+ { array_filters: [{ 'i.y' => 3 }] }
3109
+ end
3110
+
3111
+ it 'applies the arrayFilters' do
3112
+ expect(result.matched_count).to eq(1)
3113
+ expect(result.modified_count).to eq(1)
3114
+ expect(authorized_collection.find(selector).first['x'].last['y']).to eq(5)
3115
+ end
3116
+ end
3117
+
3118
+ context 'when a String key is used' do
3119
+
3120
+ let(:options) do
3121
+ { 'array_filters' => [{ 'i.y' => 3 }] }
3122
+ end
3123
+
3124
+ it 'applies the arrayFilters' do
3125
+ expect(result.matched_count).to eq(1)
3126
+ expect(result.modified_count).to eq(1)
3127
+ expect(authorized_collection.find(selector).first['x'].last['y']).to eq(5)
3128
+ end
3129
+ end
3130
+ end
3131
+
3132
+ context 'when the server does not support arrayFilters' do
3133
+ max_server_version '3.4'
3134
+
3135
+ let(:result) do
3136
+ authorized_collection.update_one(selector,
3137
+ { '$set' => { 'x.$[i].y' => 5 } },
3138
+ options)
3139
+ end
3140
+
3141
+ context 'when a Symbol key is used' do
3142
+
3143
+ let(:options) do
3144
+ { array_filters: [{ 'i.y' => 3 }] }
3145
+ end
3146
+
3147
+ it 'raises an exception' do
3148
+ expect {
3149
+ result
3150
+ }.to raise_exception(Mongo::Error::UnsupportedArrayFilters)
3151
+ end
3152
+ end
3153
+
3154
+ context 'when a String key is used' do
3155
+
3156
+ let(:options) do
3157
+ { 'array_filters' => [{ 'i.y' => 3 }] }
3158
+ end
3159
+
3160
+ it 'raises an exception' do
3161
+ expect {
3162
+ result
3163
+ }.to raise_exception(Mongo::Error::UnsupportedArrayFilters)
3164
+ end
3165
+ end
3166
+ end
3167
+ end
3168
+
3169
+ context 'when the documents are sent with OP_MSG' do
3170
+ min_server_fcv '3.6'
3171
+
3172
+ let(:documents) do
3173
+ [{ '_id' => 1, 'name' => '1'*16777191 }, { '_id' => 'y' }]
3174
+ end
3175
+
3176
+ before do
3177
+ authorized_collection.insert_many([{ field: 'test1' }, { field: 'test1' }])
3178
+ client[TEST_COLL].update_one({ a: 1 }, {'$set' => { 'name' => '1'*16777149 }})
3179
+ end
3180
+
3181
+ let(:update_events) do
3182
+ subscriber.started_events.select { |e| e.command_name == 'update' }
3183
+ end
3184
+
3185
+ it 'sends the documents in one OP_MSG' do
3186
+ expect(update_events.size).to eq(1)
3187
+ end
3188
+ end
3189
+
3190
+ context 'when a session is provided' do
3191
+
3192
+ before do
3193
+ authorized_collection.insert_many([{ field: 'test1' }, { field: 'test1' }])
3194
+ end
3195
+
3196
+ let(:session) do
3197
+ authorized_client.start_session
3198
+ end
3199
+
3200
+ let(:operation) do
3201
+ authorized_collection.update_one({ field: 'test' }, { '$set'=> { field: 'testing' } }, session: session)
3202
+ end
3203
+
3204
+ let(:failed_operation) do
3205
+ authorized_collection.update_one({ '$._id' => 1 }, { '$set'=> { field: 'testing' } }, session: session)
3206
+ end
3207
+
3208
+ let(:client) do
3209
+ authorized_client
3210
+ end
3211
+
3212
+ it_behaves_like 'an operation using a session'
3213
+ it_behaves_like 'a failed operation using a session'
3214
+ end
3215
+
3216
+ context 'when unacknowledged writes is used with an explicit session' do
3217
+
3218
+ let(:collection_with_unacknowledged_write_concern) do
3219
+ authorized_collection.with(write: { w: 0 })
3220
+ end
3221
+
3222
+ let(:operation) do
3223
+ collection_with_unacknowledged_write_concern.update_one({ a: 1 }, { '$set' => { x: 1 } }, session: session)
3224
+ end
3225
+
3226
+ it_behaves_like 'an explicit session with an unacknowledged write'
3227
+ end
3228
+
3229
+ context 'when unacknowledged writes is used with an implicit session' do
3230
+
3231
+ let(:collection_with_unacknowledged_write_concern) do
3232
+ client.with(write: { w: 0 })[TEST_COLL]
3233
+ end
3234
+
3235
+ let(:operation) do
3236
+ collection_with_unacknowledged_write_concern.update_one({ a: 1 }, { '$set' => { x: 1 }})
3237
+ end
3238
+
3239
+ it_behaves_like 'an implicit session with an unacknowledged write'
3240
+ end
3241
+
3242
+ context 'when various options passed in' do
3243
+ # w: 2 requires a replica set
3244
+ require_topology :replica_set
3245
+
3246
+ # https://jira.mongodb.org/browse/RUBY-2306
3247
+ min_server_fcv '3.6'
3248
+
3249
+ before do
3250
+ collection.insert_many([{ field: 'test1' }, { field: 'test2' }], session: session)
3251
+ end
3252
+
3253
+ let(:session) do
3254
+ authorized_client.start_session
3255
+ end
3256
+
3257
+ let(:collection) do
3258
+ authorized_collection.with(write_concern: {w: 1})
3259
+ end
3260
+
3261
+ let(:events) do
3262
+ subscriber.command_started_events('update')
3263
+ end
3264
+
3265
+ let!(:command) do
3266
+ Utils.get_command_event(authorized_client, 'update') do |client|
3267
+ collection.update_one(selector, { '$set'=> { field: 'testing' } }, session: session,
3268
+ write_concern: {w: 2}, bypass_document_validation: true, :return_document => :after,
3269
+ upsert: true)
3270
+ end.command
3271
+ end
3272
+
3273
+ it 'updates one successfully with correct options sent to server' do
3274
+ expect(events.length).to eq(1)
3275
+ expect(command[:writeConcern]).to_not be_nil
3276
+ expect(command[:writeConcern][:w]).to eq(2)
3277
+ expect(collection.options[:write_concern]).to eq(w:1)
3278
+ expect(command[:bypassDocumentValidation]).to be(true)
3279
+ expect(command[:updates][0][:upsert]).to be(true)
3280
+ end
3281
+ end
3282
+ end
3283
+
3284
+ describe '#find_one_and_delete' do
3285
+
3286
+ before do
3287
+ authorized_collection.insert_many([{ field: 'test1' }])
3288
+ end
3289
+
3290
+ let(:selector) do
3291
+ { field: 'test1' }
3292
+ end
3293
+
3294
+ context 'when a matching document is found' do
3295
+
3296
+ context 'when a session is provided' do
3297
+
3298
+ let(:operation) do
3299
+ authorized_collection.find_one_and_delete(selector, session: session)
3300
+ end
3301
+
3302
+ let(:failed_operation) do
3303
+ authorized_collection.find_one_and_delete({ '$._id' => 1 }, session: session)
3304
+ end
3305
+
3306
+ let(:session) do
3307
+ authorized_client.start_session
3308
+ end
3309
+
3310
+ let(:client) do
3311
+ authorized_client
3312
+ end
3313
+
3314
+ it_behaves_like 'an operation using a session'
3315
+ it_behaves_like 'a failed operation using a session'
3316
+ end
3317
+
3318
+ context 'when no options are provided' do
3319
+
3320
+ let!(:document) do
3321
+ authorized_collection.find_one_and_delete(selector)
3322
+ end
3323
+
3324
+ it 'deletes the document from the database' do
3325
+ expect(authorized_collection.find.to_a).to be_empty
3326
+ end
3327
+
3328
+ it 'returns the document' do
3329
+ expect(document['field']).to eq('test1')
3330
+ end
3331
+ end
3332
+
3333
+ context 'when a projection is provided' do
3334
+
3335
+ let!(:document) do
3336
+ authorized_collection.find_one_and_delete(selector, projection: { _id: 1 })
3337
+ end
3338
+
3339
+ it 'deletes the document from the database' do
3340
+ expect(authorized_collection.find.to_a).to be_empty
3341
+ end
3342
+
3343
+ it 'returns the document with limited fields' do
3344
+ expect(document['field']).to be_nil
3345
+ expect(document['_id']).to_not be_nil
3346
+ end
3347
+ end
3348
+
3349
+ context 'when a sort is provided' do
3350
+
3351
+ let!(:document) do
3352
+ authorized_collection.find_one_and_delete(selector, sort: { field: 1 })
3353
+ end
3354
+
3355
+ it 'deletes the document from the database' do
3356
+ expect(authorized_collection.find.to_a).to be_empty
3357
+ end
3358
+
3359
+ it 'returns the document with limited fields' do
3360
+ expect(document['field']).to eq('test1')
3361
+ end
3362
+ end
3363
+
3364
+ context 'when max_time_ms is provided' do
3365
+
3366
+ it 'includes the max_time_ms value in the command' do
3367
+ expect {
3368
+ authorized_collection.find_one_and_delete(selector, max_time_ms: 0.1)
3369
+ }.to raise_error(Mongo::Error::OperationFailure)
3370
+ end
3371
+ end
3372
+ end
3373
+
3374
+ context 'when no matching document is found' do
3375
+
3376
+ let(:selector) do
3377
+ { field: 'test5' }
3378
+ end
3379
+
3380
+ let!(:document) do
3381
+ authorized_collection.find_one_and_delete(selector)
3382
+ end
3383
+
3384
+ it 'returns nil' do
3385
+ expect(document).to be_nil
3386
+ end
3387
+ end
3388
+
3389
+ context 'when the operation fails' do
3390
+
3391
+ let(:result) do
3392
+ authorized_collection.find_one_and_delete(selector, max_time_ms: 0.1)
3393
+ end
3394
+
3395
+ it 'raises an OperationFailure' do
3396
+ expect {
3397
+ result
3398
+ }.to raise_exception(Mongo::Error::OperationFailure)
3399
+ end
3400
+ end
3401
+
3402
+ context 'when write_concern is provided' do
3403
+ min_server_fcv '3.2'
3404
+ require_topology :single
3405
+
3406
+ it 'uses the write concern' do
3407
+ expect {
3408
+ authorized_collection.find_one_and_delete(selector,
3409
+ write_concern: { w: 2 })
3410
+ }.to raise_error(Mongo::Error::OperationFailure)
3411
+ end
3412
+ end
3413
+
3414
+ context 'when the collection has a write concern' do
3415
+ min_server_fcv '3.2'
3416
+ require_topology :single
3417
+
3418
+ let(:collection) do
3419
+ authorized_collection.with(write: { w: 2 })
3420
+ end
3421
+
3422
+ it 'uses the write concern' do
3423
+ expect {
3424
+ collection.find_one_and_delete(selector,
3425
+ write_concern: { w: 2 })
3426
+ }.to raise_error(Mongo::Error::OperationFailure)
3427
+ end
3428
+ end
3429
+
3430
+ context 'when collation is specified' do
3431
+
3432
+ let(:selector) do
3433
+ { name: 'BANG' }
3434
+ end
3435
+
3436
+ let(:result) do
3437
+ authorized_collection.find_one_and_delete(selector, options)
3438
+ end
3439
+
3440
+ before do
3441
+ authorized_collection.insert_one(name: 'bang')
3442
+ end
3443
+
3444
+ let(:options) do
3445
+ { collation: { locale: 'en_US', strength: 2 } }
3446
+ end
3447
+
3448
+ context 'when the server selected supports collations' do
3449
+ min_server_fcv '3.4'
3450
+
3451
+ it 'applies the collation' do
3452
+ expect(result['name']).to eq('bang')
3453
+ expect(authorized_collection.find(name: 'bang').count).to eq(0)
3454
+ end
3455
+ end
3456
+
3457
+ context 'when the server selected does not support collations' do
3458
+ max_server_version '3.2'
3459
+
3460
+ it 'raises an exception' do
3461
+ expect {
3462
+ result
3463
+ }.to raise_exception(Mongo::Error::UnsupportedCollation)
3464
+ end
3465
+
3466
+ context 'when a String key is used' do
3467
+
3468
+ let(:options) do
3469
+ { 'collation' => { locale: 'en_US', strength: 2 } }
3470
+ end
3471
+
3472
+ it 'raises an exception' do
3473
+ expect {
3474
+ result
3475
+ }.to raise_exception(Mongo::Error::UnsupportedCollation)
3476
+ end
3477
+ end
3478
+ end
3479
+ end
3480
+
3481
+ context 'when collation is not specified' do
3482
+
3483
+ let(:selector) do
3484
+ { name: 'BANG' }
3485
+ end
3486
+
3487
+ let(:result) do
3488
+ authorized_collection.find_one_and_delete(selector)
3489
+ end
3490
+
3491
+ before do
3492
+ authorized_collection.insert_one(name: 'bang')
3493
+ end
3494
+
3495
+ it 'does not apply the collation' do
3496
+ expect(result).to be_nil
3497
+ end
3498
+ end
3499
+
3500
+ context 'when various options passed in' do
3501
+ # w: 2 requires a replica set
3502
+ require_topology :replica_set
3503
+
3504
+ # https://jira.mongodb.org/browse/RUBY-2306
3505
+ min_server_fcv '3.6'
3506
+
3507
+ before do
3508
+ authorized_collection.delete_many
3509
+ authorized_collection.insert_many([{ name: 'test1' }, { name: 'test2' }])
3510
+ end
3511
+
3512
+ let(:collection) do
3513
+ authorized_collection.with(write_concern: {w: 2})
3514
+ end
3515
+
3516
+ let(:session) do
3517
+ authorized_client.start_session
3518
+ end
3519
+
3520
+ let!(:command) do
3521
+ Utils.get_command_event(authorized_client, 'findAndModify') do |client|
3522
+ collection.find_one_and_delete(selector, session: session, write_concern: {w: 2},
3523
+ bypass_document_validation: true, max_time_ms: 300)
3524
+ end.command
3525
+ end
3526
+
3527
+ let(:events) do
3528
+ subscriber.command_started_events('findAndModify')
3529
+ end
3530
+
3531
+ it 'finds and deletes successfully with correct options sent to server' do
3532
+ expect(events.length).to eq(1)
3533
+ expect(command[:writeConcern]).to_not be_nil
3534
+ expect(command[:writeConcern][:w]).to eq(2)
3535
+ expect(command[:bypassDocumentValidation]).to eq(true)
3536
+ expect(command[:maxTimeMS]).to eq(300)
3537
+ end
3538
+ end
3539
+ end
3540
+
3541
+ describe '#find_one_and_update' do
3542
+
3543
+ let(:selector) do
3544
+ { field: 'test1' }
3545
+ end
3546
+
3547
+ before do
3548
+ authorized_collection.insert_many([{ field: 'test1' }])
3549
+ end
3550
+
3551
+ context 'when a matching document is found' do
3552
+
3553
+ context 'when no options are provided' do
3554
+
3555
+ let(:document) do
3556
+ authorized_collection.find_one_and_update(selector, { '$set' => { field: 'testing' }})
3557
+ end
3558
+
3559
+ it 'returns the original document' do
3560
+ expect(document['field']).to eq('test1')
3561
+ end
3562
+ end
3563
+
3564
+ context 'when a session is provided' do
3565
+
3566
+ let(:operation) do
3567
+ authorized_collection.find_one_and_update(selector, { '$set' => { field: 'testing' }}, session: session)
3568
+ end
3569
+
3570
+ let(:failed_operation) do
3571
+ authorized_collection.find_one_and_update({ '$._id' => 1 }, { '$set' => { field: 'testing' }}, session: session)
3572
+ end
3573
+
3574
+ let(:session) do
3575
+ authorized_client.start_session
3576
+ end
3577
+
3578
+ let(:client) do
3579
+ authorized_client
3580
+ end
3581
+
3582
+ it_behaves_like 'an operation using a session'
3583
+ it_behaves_like 'a failed operation using a session'
3584
+ end
3585
+
3586
+ context 'when no options are provided' do
3587
+
3588
+ let(:document) do
3589
+ authorized_collection.find_one_and_update(selector, { '$set' => { field: 'testing' }})
3590
+ end
3591
+
3592
+ it 'returns the original document' do
3593
+ expect(document['field']).to eq('test1')
3594
+ end
3595
+ end
3596
+
3597
+ context 'when return_document options are provided' do
3598
+
3599
+ context 'when return_document is :after' do
3600
+
3601
+ let(:document) do
3602
+ authorized_collection.find_one_and_update(selector, { '$set' => { field: 'testing' }}, :return_document => :after)
3603
+ end
3604
+
3605
+ it 'returns the new document' do
3606
+ expect(document['field']).to eq('testing')
3607
+ end
3608
+ end
3609
+
3610
+ context 'when return_document is :before' do
3611
+
3612
+ let(:document) do
3613
+ authorized_collection.find_one_and_update(selector, { '$set' => { field: 'testing' }}, :return_document => :before)
3614
+ end
3615
+
3616
+ it 'returns the original document' do
3617
+ expect(document['field']).to eq('test1')
3618
+ end
3619
+ end
3620
+ end
3621
+
3622
+ context 'when a projection is provided' do
3623
+
3624
+ let(:document) do
3625
+ authorized_collection.find_one_and_update(selector, { '$set' => { field: 'testing' }}, projection: { _id: 1 })
3626
+ end
3627
+
3628
+ it 'returns the document with limited fields' do
3629
+ expect(document['field']).to be_nil
3630
+ expect(document['_id']).to_not be_nil
3631
+ end
3632
+ end
3633
+
3634
+ context 'when a sort is provided' do
3635
+
3636
+ let(:document) do
3637
+ authorized_collection.find_one_and_update(selector, { '$set' => { field: 'testing' }}, sort: { field: 1 })
3638
+ end
3639
+
3640
+ it 'returns the original document' do
3641
+ expect(document['field']).to eq('test1')
3642
+ end
3643
+ end
3644
+ end
3645
+
3646
+ context 'when max_time_ms is provided' do
3647
+
3648
+ it 'includes the max_time_ms value in the command' do
3649
+ expect {
3650
+ authorized_collection.find_one_and_update(selector, { '$set' => { field: 'testing' }}, max_time_ms: 0.1)
3651
+ }.to raise_error(Mongo::Error::OperationFailure)
3652
+ end
3653
+ end
3654
+
3655
+ context 'when no matching document is found' do
3656
+
3657
+ let(:selector) do
3658
+ { field: 'test5' }
3659
+ end
3660
+
3661
+ let(:document) do
3662
+ authorized_collection.find_one_and_update(selector, { '$set' => { field: 'testing' }})
3663
+ end
3664
+
3665
+ it 'returns nil' do
3666
+ expect(document).to be_nil
3667
+ end
3668
+ end
3669
+
3670
+ context 'when no matching document is found' do
3671
+
3672
+ context 'when no upsert options are provided' do
3673
+
3674
+ let(:selector) do
3675
+ { field: 'test5' }
3676
+ end
3677
+
3678
+ let(:document) do
3679
+ authorized_collection.find_one_and_update(selector, { '$set' => { field: 'testing' }})
3680
+ end
3681
+
3682
+ it 'returns nil' do
3683
+ expect(document).to be_nil
3684
+ end
3685
+ end
3686
+
3687
+ context 'when upsert options are provided' do
3688
+
3689
+ let(:selector) do
3690
+ { field: 'test5' }
3691
+ end
3692
+
3693
+ let(:document) do
3694
+ authorized_collection.find_one_and_update(selector, { '$set' => { field: 'testing' }}, :upsert => true, :return_document => :after)
3695
+ end
3696
+
3697
+ it 'returns the new document' do
3698
+ expect(document['field']).to eq('testing')
3699
+ end
3700
+ end
3701
+ end
3702
+
3703
+ context 'when the operation fails' do
3704
+
3705
+ let(:result) do
3706
+ authorized_collection.find_one_and_update(selector, { '$set' => { field: 'testing' }}, max_time_ms: 0.1)
3707
+ end
3708
+
3709
+ it 'raises an OperationFailure' do
3710
+ expect {
3711
+ result
3712
+ }.to raise_exception(Mongo::Error::OperationFailure)
3713
+ end
3714
+ end
3715
+
3716
+ context 'when collection has a validator' do
3717
+ min_server_fcv '3.2'
3718
+
3719
+ around(:each) do |spec|
3720
+ authorized_client[:validating].drop
3721
+ authorized_client[:validating,
3722
+ :validator => { :a => { '$exists' => true } }].tap do |c|
3723
+ c.create
3724
+ end
3725
+ spec.run
3726
+ collection_with_validator.drop
3727
+ end
3728
+
3729
+ before do
3730
+ collection_with_validator.insert_one({ a: 1 })
3731
+ end
3732
+
3733
+ context 'when the document is valid' do
3734
+
3735
+ let(:result) do
3736
+ collection_with_validator.find_one_and_update(
3737
+ { a: 1 }, { '$inc' => { :a => 1 } }, :return_document => :after)
3738
+ end
3739
+
3740
+ it 'updates successfully' do
3741
+ expect(result['a']).to eq(2)
3742
+ end
3743
+ end
3744
+
3745
+ context 'when the document is invalid' do
3746
+
3747
+ context 'when bypass_document_validation is not set' do
3748
+
3749
+ let(:result2) do
3750
+ collection_with_validator.find_one_and_update(
3751
+ { a: 1 }, { '$unset' => { :a => '' } }, :return_document => :after)
3752
+ end
3753
+
3754
+ it 'raises OperationFailure' do
3755
+ expect {
3756
+ result2
3757
+ }.to raise_exception(Mongo::Error::OperationFailure)
3758
+ end
3759
+ end
3760
+
3761
+ context 'when bypass_document_validation is true' do
3762
+
3763
+ let(:result3) do
3764
+ collection_with_validator.find_one_and_update(
3765
+ { a: 1 }, { '$unset' => { :a => '' } },
3766
+ :bypass_document_validation => true,
3767
+ :return_document => :after)
3768
+ end
3769
+
3770
+ it 'updates successfully' do
3771
+ expect(result3['a']).to be_nil
3772
+ end
3773
+ end
3774
+ end
3775
+ end
3776
+
3777
+ context 'when write_concern is provided' do
3778
+ min_server_fcv '3.2'
3779
+ require_topology :single
3780
+
3781
+ it 'uses the write concern' do
3782
+ expect {
3783
+ authorized_collection.find_one_and_update(selector,
3784
+ { '$set' => { field: 'testing' }},
3785
+ write_concern: { w: 2 })
3786
+ }.to raise_error(Mongo::Error::OperationFailure)
3787
+ end
3788
+ end
3789
+
3790
+ context 'when the collection has a write concern' do
3791
+ min_server_fcv '3.2'
3792
+ require_topology :single
3793
+
3794
+ let(:collection) do
3795
+ authorized_collection.with(write: { w: 2 })
3796
+ end
3797
+
3798
+ it 'uses the write concern' do
3799
+ expect {
3800
+ collection.find_one_and_update(selector,
3801
+ { '$set' => { field: 'testing' }},
3802
+ write_concern: { w: 2 })
3803
+ }.to raise_error(Mongo::Error::OperationFailure)
3804
+ end
3805
+ end
3806
+
3807
+ context 'when a collation is specified' do
3808
+
3809
+ let(:selector) do
3810
+ { name: 'BANG' }
3811
+ end
3812
+
3813
+ let(:result) do
3814
+ authorized_collection.find_one_and_update(selector,
3815
+ { '$set' => { other: 'doink' } },
3816
+ options)
3817
+ end
3818
+
3819
+ before do
3820
+ authorized_collection.insert_one(name: 'bang')
3821
+ end
3822
+
3823
+ let(:options) do
3824
+ { collation: { locale: 'en_US', strength: 2 } }
3825
+ end
3826
+
3827
+ context 'when the server selected supports collations' do
3828
+ min_server_fcv '3.4'
3829
+
3830
+ it 'applies the collation' do
3831
+ expect(result['name']).to eq('bang')
3832
+ expect(authorized_collection.find({ name: 'bang' }, limit: -1).first['other']).to eq('doink')
3833
+ end
3834
+ end
3835
+
3836
+ context 'when the server selected does not support collations' do
3837
+ max_server_version '3.2'
3838
+
3839
+ it 'raises an exception' do
3840
+ expect {
3841
+ result
3842
+ }.to raise_exception(Mongo::Error::UnsupportedCollation)
3843
+ end
3844
+
3845
+ context 'when a String key is used' do
3846
+
3847
+ let(:options) do
3848
+ { 'collation' => { locale: 'en_US', strength: 2 } }
3849
+ end
3850
+
3851
+ it 'raises an exception' do
3852
+ expect {
3853
+ result
3854
+ }.to raise_exception(Mongo::Error::UnsupportedCollation)
3855
+ end
3856
+ end
3857
+ end
3858
+ end
3859
+
3860
+ context 'when there is no collation specified' do
3861
+
3862
+ let(:selector) do
3863
+ { name: 'BANG' }
3864
+ end
3865
+
3866
+ let(:result) do
3867
+ authorized_collection.find_one_and_update(selector, { '$set' => { other: 'doink' } })
3868
+ end
3869
+
3870
+ before do
3871
+ authorized_collection.insert_one(name: 'bang')
3872
+ end
3873
+
3874
+ it 'does not apply the collation' do
3875
+ expect(result).to be_nil
3876
+ end
3877
+ end
3878
+
3879
+ context 'when arrayFilters is provided' do
3880
+
3881
+ let(:selector) do
3882
+ { _id: 0 }
3883
+ end
3884
+
3885
+ context 'when the server supports arrayFilters' do
3886
+ min_server_fcv '3.6'
3887
+
3888
+ before do
3889
+ authorized_collection.insert_one(_id: 0, x: [{ y: 1 }, { y: 2 }, { y: 3 }])
3890
+ end
3891
+
3892
+ let(:result) do
3893
+ authorized_collection.find_one_and_update(selector,
3894
+ { '$set' => { 'x.$[i].y' => 5 } },
3895
+ options)
3896
+ end
3897
+
3898
+ context 'when a Symbol key is used' do
3899
+
3900
+ let(:options) do
3901
+ { array_filters: [{ 'i.y' => 3 }] }
3902
+ end
3903
+
3904
+
3905
+ it 'applies the arrayFilters' do
3906
+ expect(result['x']).to eq([{ 'y' => 1 }, { 'y' => 2 }, { 'y' => 3 }])
3907
+ expect(authorized_collection.find(selector).first['x'].last['y']).to eq(5)
3908
+ end
3909
+ end
3910
+
3911
+ context 'when a String key is used' do
3912
+
3913
+ let(:options) do
3914
+ { 'array_filters' => [{ 'i.y' => 3 }] }
3915
+ end
3916
+
3917
+ it 'applies the arrayFilters' do
3918
+ expect(result['x']).to eq([{ 'y' => 1 }, { 'y' => 2 }, { 'y' => 3 }])
3919
+ expect(authorized_collection.find(selector).first['x'].last['y']).to eq(5)
3920
+ end
3921
+ end
3922
+ end
3923
+
3924
+ context 'when the server selected does not support arrayFilters' do
3925
+ max_server_version '3.4'
3926
+
3927
+ let(:result) do
3928
+ authorized_collection.find_one_and_update(selector,
3929
+ { '$set' => { 'x.$[i].y' => 5 } },
3930
+ options)
3931
+ end
3932
+
3933
+ context 'when a Symbol key is used' do
3934
+
3935
+ let(:options) do
3936
+ { array_filters: [{ 'i.y' => 3 }] }
3937
+ end
3938
+
3939
+ it 'raises an exception' do
3940
+ expect {
3941
+ result
3942
+ }.to raise_exception(Mongo::Error::UnsupportedArrayFilters)
3943
+ end
3944
+ end
3945
+
3946
+ context 'when a String key is used' do
3947
+
3948
+ let(:options) do
3949
+ { 'array_filters' => [{ 'i.y' => 3 }] }
3950
+ end
3951
+
3952
+ it 'raises an exception' do
3953
+ expect {
3954
+ result
3955
+ }.to raise_exception(Mongo::Error::UnsupportedArrayFilters)
3956
+ end
3957
+ end
3958
+ end
3959
+ end
3960
+
3961
+ context 'when various options passed in' do
3962
+ # w: 2 requires a replica set
3963
+ require_topology :replica_set
3964
+
3965
+ # https://jira.mongodb.org/browse/RUBY-2306
3966
+ min_server_fcv '3.6'
3967
+
3968
+ let(:session) do
3969
+ authorized_client.start_session
3970
+ end
3971
+
3972
+ let(:events) do
3973
+ subscriber.command_started_events('findAndModify')
3974
+ end
3975
+
3976
+ let(:collection) do
3977
+ authorized_collection.with(write_concern: {w: 2})
3978
+ end
3979
+
3980
+ let(:selector) do
3981
+ {field: 'test1'}
3982
+ end
3983
+
3984
+ before do
3985
+ collection.insert_one({field: 'test1'}, session: session)
3986
+ end
3987
+
3988
+ let!(:command) do
3989
+ Utils.get_command_event(authorized_client, 'findAndModify') do |client|
3990
+ collection.find_one_and_update(selector, { '$set' => {field: 'testing'}},
3991
+ :return_document => :after, write_concern: {w: 1}, upsert: true,
3992
+ bypass_document_validation: true, max_time_ms: 100, session: session)
3993
+ end.command
3994
+ end
3995
+
3996
+ it 'find and updates successfully with correct options sent to server' do
3997
+ expect(events.length).to eq(1)
3998
+ expect(command[:writeConcern]).to_not be_nil
3999
+ expect(command[:writeConcern][:w]).to eq(1)
4000
+ expect(command[:upsert]).to eq(true)
4001
+ expect(command[:bypassDocumentValidation]).to be(true)
4002
+ expect(command[:maxTimeMS]).to eq(100)
4003
+ end
4004
+ end
4005
+ end
4006
+
4007
+ describe '#find_one_and_replace' do
4008
+
4009
+ before do
4010
+ authorized_collection.insert_many([{ field: 'test1', other: 'sth' }])
4011
+ end
4012
+
4013
+ let(:selector) do
4014
+ { field: 'test1' }
4015
+ end
4016
+
4017
+ context 'when a matching document is found' do
4018
+
4019
+ context 'when no options are provided' do
4020
+
4021
+ let(:document) do
4022
+ authorized_collection.find_one_and_replace(selector, { field: 'testing' })
4023
+ end
4024
+
4025
+ it 'returns the original document' do
4026
+ expect(document['field']).to eq('test1')
4027
+ end
4028
+ end
4029
+
4030
+ context 'when a session is provided' do
4031
+
4032
+ let(:operation) do
4033
+ authorized_collection.find_one_and_replace(selector, { field: 'testing' }, session: session)
4034
+ end
4035
+
4036
+ let(:failed_operation) do
4037
+ authorized_collection.find_one_and_replace({ '$._id' => 1}, { field: 'testing' }, session: session)
4038
+ end
4039
+
4040
+ let(:session) do
4041
+ authorized_client.start_session
4042
+ end
4043
+
4044
+ let(:client) do
4045
+ authorized_client
4046
+ end
4047
+
4048
+ it_behaves_like 'an operation using a session'
4049
+ it_behaves_like 'a failed operation using a session'
4050
+ end
4051
+
4052
+ context 'when return_document options are provided' do
4053
+
4054
+ context 'when return_document is :after' do
4055
+
4056
+ let(:document) do
4057
+ authorized_collection.find_one_and_replace(selector, { field: 'testing' }, :return_document => :after)
4058
+ end
4059
+
4060
+ it 'returns the new document' do
4061
+ expect(document['field']).to eq('testing')
4062
+ end
4063
+ end
4064
+
4065
+ context 'when return_document is :before' do
4066
+
4067
+ let(:document) do
4068
+ authorized_collection.find_one_and_replace(selector, { field: 'testing' }, :return_document => :before)
4069
+ end
4070
+
4071
+ it 'returns the original document' do
4072
+ expect(document['field']).to eq('test1')
4073
+ end
4074
+ end
4075
+ end
4076
+
4077
+ context 'when a projection is provided' do
4078
+
4079
+ let(:document) do
4080
+ authorized_collection.find_one_and_replace(selector, { field: 'testing' }, projection: { _id: 1 })
4081
+ end
4082
+
4083
+ it 'returns the document with limited fields' do
4084
+ expect(document['field']).to be_nil
4085
+ expect(document['_id']).to_not be_nil
4086
+ end
4087
+ end
4088
+
4089
+ context 'when a sort is provided' do
4090
+
4091
+ let(:document) do
4092
+ authorized_collection.find_one_and_replace(selector, { field: 'testing' }, :sort => { field: 1 })
4093
+ end
4094
+
4095
+ it 'returns the original document' do
4096
+ expect(document['field']).to eq('test1')
4097
+ end
4098
+ end
4099
+ end
4100
+
4101
+ context 'when no matching document is found' do
4102
+
4103
+ context 'when no upsert options are provided' do
4104
+
4105
+ let(:selector) do
4106
+ { field: 'test5' }
4107
+ end
4108
+
4109
+ let(:document) do
4110
+ authorized_collection.find_one_and_replace(selector, { field: 'testing' })
4111
+ end
4112
+
4113
+ it 'returns nil' do
4114
+ expect(document).to be_nil
4115
+ end
4116
+ end
4117
+
4118
+ context 'when upsert options are provided' do
4119
+
4120
+ let(:selector) do
4121
+ { field: 'test5' }
4122
+ end
4123
+
4124
+ let(:document) do
4125
+ authorized_collection.find_one_and_replace(selector, { field: 'testing' }, :upsert => true, :return_document => :after)
4126
+ end
4127
+
4128
+ it 'returns the new document' do
4129
+ expect(document['field']).to eq('testing')
4130
+ end
4131
+ end
4132
+ end
4133
+
4134
+ context 'when max_time_ms is provided' do
4135
+
4136
+ it 'includes the max_time_ms value in the command' do
4137
+ expect {
4138
+ authorized_collection.find_one_and_replace(selector, { field: 'testing' }, max_time_ms: 0.1)
4139
+ }.to raise_error(Mongo::Error::OperationFailure)
4140
+ end
4141
+ end
4142
+
4143
+ context 'when the operation fails' do
4144
+
4145
+ let(:result) do
4146
+ authorized_collection.find_one_and_replace(selector, { field: 'testing' }, max_time_ms: 0.1)
4147
+ end
4148
+
4149
+ it 'raises an OperationFailure' do
4150
+ expect {
4151
+ result
4152
+ }.to raise_exception(Mongo::Error::OperationFailure)
4153
+ end
4154
+ end
4155
+
4156
+ context 'when collection has a validator' do
4157
+ min_server_fcv '3.2'
4158
+
4159
+ around(:each) do |spec|
4160
+ authorized_client[:validating].drop
4161
+ authorized_client[:validating,
4162
+ :validator => { :a => { '$exists' => true } }].tap do |c|
4163
+ c.create
4164
+ end
4165
+ spec.run
4166
+ collection_with_validator.drop
4167
+ end
4168
+
4169
+ before do
4170
+ collection_with_validator.insert_one({ a: 1 })
4171
+ end
4172
+
4173
+ context 'when the document is valid' do
4174
+
4175
+ let(:result) do
4176
+ collection_with_validator.find_one_and_replace(
4177
+ { a: 1 }, { a: 5 }, :return_document => :after)
4178
+ end
4179
+
4180
+ it 'replaces successfully when document is valid' do
4181
+ expect(result[:a]).to eq(5)
4182
+ end
4183
+ end
4184
+
4185
+ context 'when the document is invalid' do
4186
+
4187
+ context 'when bypass_document_validation is not set' do
4188
+
4189
+ let(:result2) do
4190
+ collection_with_validator.find_one_and_replace(
4191
+ { a: 1 }, { x: 5 }, :return_document => :after)
4192
+ end
4193
+
4194
+ it 'raises OperationFailure' do
4195
+ expect {
4196
+ result2
4197
+ }.to raise_exception(Mongo::Error::OperationFailure)
4198
+ end
4199
+ end
4200
+
4201
+ context 'when bypass_document_validation is true' do
4202
+
4203
+ let(:result3) do
4204
+ collection_with_validator.find_one_and_replace(
4205
+ { a: 1 }, { x: 1 }, :bypass_document_validation => true,
4206
+ :return_document => :after)
4207
+ end
4208
+
4209
+ it 'replaces successfully' do
4210
+ expect(result3[:x]).to eq(1)
4211
+ expect(result3[:a]).to be_nil
4212
+ end
4213
+ end
4214
+ end
4215
+ end
4216
+
4217
+ context 'when write_concern is provided' do
4218
+ min_server_fcv '3.2'
4219
+ require_topology :single
4220
+
4221
+ it 'uses the write concern' do
4222
+ expect {
4223
+ authorized_collection.find_one_and_replace(selector,
4224
+ { field: 'testing' },
4225
+ write_concern: { w: 2 })
4226
+ }.to raise_error(Mongo::Error::OperationFailure)
4227
+ end
4228
+ end
4229
+
4230
+ context 'when the collection has a write concern' do
4231
+ min_server_fcv '3.2'
4232
+ require_topology :single
4233
+
4234
+ let(:collection) do
4235
+ authorized_collection.with(write: { w: 2 })
4236
+ end
4237
+
4238
+ it 'uses the write concern' do
4239
+ expect {
4240
+ collection.find_one_and_replace(selector,
4241
+ { field: 'testing' },
4242
+ write_concern: { w: 2 })
4243
+ }.to raise_error(Mongo::Error::OperationFailure)
4244
+ end
4245
+ end
4246
+
4247
+ context 'when collation is provided' do
4248
+
4249
+ let(:selector) do
4250
+ { name: 'BANG' }
4251
+ end
4252
+
4253
+ let(:result) do
4254
+ authorized_collection.find_one_and_replace(selector,
4255
+ { name: 'doink' },
4256
+ options)
4257
+ end
4258
+
4259
+ before do
4260
+ authorized_collection.insert_one(name: 'bang')
4261
+ end
4262
+
4263
+ let(:options) do
4264
+ { collation: { locale: 'en_US', strength: 2 } }
4265
+ end
4266
+
4267
+ context 'when the server selected supports collations' do
4268
+ min_server_fcv '3.4'
4269
+
4270
+ it 'applies the collation' do
4271
+ expect(result['name']).to eq('bang')
4272
+ expect(authorized_collection.find(name: 'doink').count).to eq(1)
4273
+ end
4274
+ end
4275
+
4276
+ context 'when the server selected does not support collations' do
4277
+ max_server_version '3.2'
4278
+
4279
+ it 'raises an exception' do
4280
+ expect {
4281
+ result
4282
+ }.to raise_exception(Mongo::Error::UnsupportedCollation)
4283
+ end
4284
+
4285
+ context 'when a String key is used' do
4286
+
4287
+ let(:options) do
4288
+ { 'collation' => { locale: 'en_US', strength: 2 } }
4289
+ end
4290
+
4291
+ it 'raises an exception' do
4292
+ expect {
4293
+ result
4294
+ }.to raise_exception(Mongo::Error::UnsupportedCollation)
4295
+ end
4296
+ end
4297
+ end
4298
+ end
4299
+
4300
+ context 'when collation is not specified' do
4301
+
4302
+ let(:selector) do
4303
+ { name: 'BANG' }
4304
+ end
4305
+
4306
+ let(:result) do
4307
+ authorized_collection.find_one_and_replace(selector, { name: 'doink' })
4308
+ end
4309
+
4310
+ before do
4311
+ authorized_collection.insert_one(name: 'bang')
4312
+ end
4313
+
4314
+ it 'does not apply the collation' do
4315
+ expect(result).to be_nil
4316
+ end
4317
+ end
4318
+
4319
+ context 'when various options passed in' do
4320
+ # https://jira.mongodb.org/browse/RUBY-2306
4321
+ min_server_fcv '3.6'
4322
+
4323
+ before do
4324
+ authorized_collection.insert_one({field: 'test1'})
4325
+ end
4326
+
4327
+ let(:session) do
4328
+ authorized_client.start_session
4329
+ end
4330
+
4331
+ let(:events) do
4332
+ subscriber.command_started_events('findAndModify')
4333
+ end
4334
+
4335
+ let(:collection) do
4336
+ authorized_collection.with(write_concern: { w: 2 })
4337
+ end
4338
+
4339
+ let!(:command) do
4340
+ Utils.get_command_event(authorized_client, 'findAndModify') do |client|
4341
+ collection.find_one_and_replace(selector, { '$set' => {field: 'test5'}},
4342
+ :return_document => :after, write_concern: {w: 1}, session: session,
4343
+ upsert: true, bypass_document_validation: false, max_time_ms: 200)
4344
+ end.command
4345
+ end
4346
+
4347
+ it 'find and replaces successfully with correct options sent to server' do
4348
+ expect(events.length).to eq(1)
4349
+ expect(command[:writeConcern]).to_not be_nil
4350
+ expect(command[:writeConcern][:w]).to eq(1)
4351
+ expect(command[:upsert]).to be(true)
4352
+ expect(command[:bypassDocumentValidation]).to be_nil
4353
+ expect(command[:maxTimeMS]).to eq(200)
4354
+ end
4355
+ end
4356
+ end
4357
+ end