mongo 2.14.0 → 2.15.0.alpha

Sign up to get free protection for your applications and to get access to all the features.
Files changed (230) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data.tar.gz.sig +0 -0
  4. data/README.md +4 -1
  5. data/Rakefile +8 -15
  6. data/lib/mongo.rb +23 -0
  7. data/lib/mongo/auth/aws/conversation.rb +1 -4
  8. data/lib/mongo/auth/base.rb +13 -7
  9. data/lib/mongo/auth/conversation_base.rb +32 -0
  10. data/lib/mongo/auth/cr/conversation.rb +6 -29
  11. data/lib/mongo/auth/gssapi/conversation.rb +4 -15
  12. data/lib/mongo/auth/ldap/conversation.rb +3 -14
  13. data/lib/mongo/auth/sasl_conversation_base.rb +1 -13
  14. data/lib/mongo/auth/scram_conversation_base.rb +7 -34
  15. data/lib/mongo/auth/user/view.rb +16 -9
  16. data/lib/mongo/auth/x509/conversation.rb +4 -25
  17. data/lib/mongo/background_thread.rb +11 -0
  18. data/lib/mongo/bulk_write.rb +21 -18
  19. data/lib/mongo/client.rb +82 -6
  20. data/lib/mongo/cluster.rb +19 -28
  21. data/lib/mongo/cluster/reapers/cursor_reaper.rb +6 -2
  22. data/lib/mongo/cluster/sdam_flow.rb +14 -0
  23. data/lib/mongo/collection.rb +8 -6
  24. data/lib/mongo/collection/view/aggregation.rb +1 -1
  25. data/lib/mongo/collection/view/change_stream.rb +1 -1
  26. data/lib/mongo/collection/view/iterable.rb +1 -1
  27. data/lib/mongo/collection/view/map_reduce.rb +2 -2
  28. data/lib/mongo/collection/view/readable.rb +42 -20
  29. data/lib/mongo/collection/view/writable.rb +14 -14
  30. data/lib/mongo/cursor.rb +2 -2
  31. data/lib/mongo/database.rb +22 -5
  32. data/lib/mongo/database/view.rb +1 -1
  33. data/lib/mongo/error.rb +9 -1
  34. data/lib/mongo/error/bulk_write_error.rb +17 -3
  35. data/lib/mongo/error/internal_driver_error.rb +22 -0
  36. data/lib/mongo/error/operation_failure.rb +21 -2
  37. data/lib/mongo/error/parser.rb +65 -12
  38. data/lib/mongo/error/server_api_conflict.rb +23 -0
  39. data/lib/mongo/error/server_api_not_supported.rb +24 -0
  40. data/lib/mongo/error/unmet_dependency.rb +21 -0
  41. data/lib/mongo/grid/fs_bucket.rb +37 -37
  42. data/lib/mongo/index/view.rb +21 -11
  43. data/lib/mongo/monitoring.rb +13 -4
  44. data/lib/mongo/monitoring/event/server_heartbeat_failed.rb +27 -16
  45. data/lib/mongo/monitoring/event/server_heartbeat_succeeded.rb +26 -15
  46. data/lib/mongo/operation.rb +2 -2
  47. data/lib/mongo/operation/collections_info.rb +18 -1
  48. data/lib/mongo/operation/collections_info/command.rb +2 -2
  49. data/lib/mongo/operation/context.rb +99 -0
  50. data/lib/mongo/operation/indexes.rb +15 -1
  51. data/lib/mongo/operation/insert/command.rb +2 -2
  52. data/lib/mongo/operation/insert/legacy.rb +2 -2
  53. data/lib/mongo/operation/insert/op_msg.rb +2 -2
  54. data/lib/mongo/operation/list_collections/result.rb +4 -1
  55. data/lib/mongo/operation/result.rb +2 -0
  56. data/lib/mongo/operation/shared/executable.rb +24 -14
  57. data/lib/mongo/operation/shared/executable_no_validate.rb +2 -2
  58. data/lib/mongo/operation/shared/op_msg_or_command.rb +1 -7
  59. data/lib/mongo/operation/shared/op_msg_or_find_command.rb +1 -7
  60. data/lib/mongo/operation/shared/polymorphic_operation.rb +39 -0
  61. data/lib/mongo/operation/shared/response_handling.rb +23 -23
  62. data/lib/mongo/operation/shared/sessions_supported.rb +13 -2
  63. data/lib/mongo/operation/shared/write.rb +8 -18
  64. data/lib/mongo/protocol/compressed.rb +51 -5
  65. data/lib/mongo/protocol/message.rb +20 -2
  66. data/lib/mongo/protocol/msg.rb +36 -11
  67. data/lib/mongo/query_cache.rb +30 -0
  68. data/lib/mongo/retryable.rb +1 -1
  69. data/lib/mongo/server.rb +7 -15
  70. data/lib/mongo/server/app_metadata.rb +52 -18
  71. data/lib/mongo/server/connection.rb +5 -0
  72. data/lib/mongo/server/connection_base.rb +13 -10
  73. data/lib/mongo/server/connection_pool.rb +6 -4
  74. data/lib/mongo/server/description.rb +4 -0
  75. data/lib/mongo/server/description/features.rb +9 -8
  76. data/lib/mongo/server/monitor.rb +20 -1
  77. data/lib/mongo/server/monitor/app_metadata.rb +1 -1
  78. data/lib/mongo/server/monitor/connection.rb +9 -10
  79. data/lib/mongo/server/pending_connection.rb +24 -6
  80. data/lib/mongo/server/push_monitor.rb +11 -1
  81. data/lib/mongo/session.rb +2 -2
  82. data/lib/mongo/session/session_pool.rb +4 -2
  83. data/lib/mongo/socket.rb +29 -4
  84. data/lib/mongo/socket/ssl.rb +8 -0
  85. data/lib/mongo/srv/monitor.rb +0 -11
  86. data/lib/mongo/uri/options_mapper.rb +38 -0
  87. data/lib/mongo/utils.rb +15 -0
  88. data/lib/mongo/version.rb +1 -1
  89. data/spec/README.md +24 -1
  90. data/spec/integration/auth_spec.rb +25 -15
  91. data/spec/integration/bulk_write_error_message_spec.rb +41 -0
  92. data/spec/integration/change_stream_spec.rb +4 -4
  93. data/spec/integration/command_monitoring_spec.rb +2 -2
  94. data/spec/integration/connection_spec.rb +2 -0
  95. data/spec/integration/docs_examples_spec.rb +8 -1
  96. data/spec/integration/fork_reconnect_spec.rb +4 -1
  97. data/spec/integration/ocsp_verifier_spec.rb +13 -7
  98. data/spec/integration/operation_failure_code_spec.rb +1 -1
  99. data/spec/integration/operation_failure_message_spec.rb +90 -0
  100. data/spec/integration/reconnect_spec.rb +1 -1
  101. data/spec/integration/sdam_error_handling_spec.rb +1 -1
  102. data/spec/integration/sdam_events_spec.rb +3 -5
  103. data/spec/integration/snappy_compression_spec.rb +25 -0
  104. data/spec/integration/srv_monitoring_spec.rb +1 -1
  105. data/spec/integration/transactions_examples_spec.rb +6 -0
  106. data/spec/integration/zlib_compression_spec.rb +1 -1
  107. data/spec/integration/zstd_compression_spec.rb +26 -0
  108. data/spec/lite_spec_helper.rb +7 -1
  109. data/spec/mongo/address_spec.rb +15 -11
  110. data/spec/mongo/auth/ldap/conversation_spec.rb +1 -1
  111. data/spec/mongo/auth/ldap_spec.rb +5 -1
  112. data/spec/mongo/auth/scram_negotiation_spec.rb +1 -1
  113. data/spec/mongo/auth/scram_spec.rb +1 -1
  114. data/spec/mongo/auth/x509/conversation_spec.rb +3 -3
  115. data/spec/mongo/client_construction_spec.rb +207 -33
  116. data/spec/mongo/client_spec.rb +17 -0
  117. data/spec/mongo/cluster_spec.rb +3 -18
  118. data/spec/mongo/collection/view/explainable_spec.rb +1 -1
  119. data/spec/mongo/collection/view/readable_spec.rb +33 -19
  120. data/spec/mongo/collection_crud_spec.rb +4357 -0
  121. data/spec/mongo/collection_ddl_spec.rb +534 -0
  122. data/spec/mongo/collection_spec.rb +5 -4859
  123. data/spec/mongo/database_spec.rb +66 -4
  124. data/spec/mongo/error/bulk_write_error_spec.rb +3 -3
  125. data/spec/mongo/error/parser_spec.rb +37 -6
  126. data/spec/mongo/index/view_spec.rb +8 -2
  127. data/spec/mongo/monitoring/event/server_heartbeat_failed_spec.rb +1 -1
  128. data/spec/mongo/monitoring/event/server_heartbeat_succeeded_spec.rb +1 -1
  129. data/spec/mongo/operation/aggregate_spec.rb +2 -1
  130. data/spec/mongo/operation/collections_info_spec.rb +4 -1
  131. data/spec/mongo/operation/command_spec.rb +6 -3
  132. data/spec/mongo/operation/create_index_spec.rb +6 -3
  133. data/spec/mongo/operation/create_user_spec.rb +6 -3
  134. data/spec/mongo/operation/delete/bulk_spec.rb +9 -6
  135. data/spec/mongo/operation/delete_spec.rb +11 -7
  136. data/spec/mongo/operation/drop_index_spec.rb +6 -2
  137. data/spec/mongo/operation/find/legacy_spec.rb +3 -1
  138. data/spec/mongo/operation/get_more_spec.rb +3 -1
  139. data/spec/mongo/operation/indexes_spec.rb +5 -1
  140. data/spec/mongo/operation/insert/bulk_spec.rb +10 -7
  141. data/spec/mongo/operation/insert_spec.rb +15 -12
  142. data/spec/mongo/operation/map_reduce_spec.rb +5 -2
  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_spec.rb +2 -0
  152. data/spec/mongo/server/connection_pool/populator_spec.rb +3 -1
  153. data/spec/mongo/server/connection_pool_spec.rb +1 -1
  154. data/spec/mongo/server/connection_spec.rb +24 -17
  155. data/spec/mongo/server/monitor/connection_spec.rb +17 -7
  156. data/spec/mongo/server/monitor_spec.rb +9 -1
  157. data/spec/mongo/server_spec.rb +15 -2
  158. data/spec/mongo/socket/ssl_spec.rb +40 -0
  159. data/spec/mongo/socket_spec.rb +2 -2
  160. data/spec/mongo/tls_context_hooks_spec.rb +37 -0
  161. data/spec/runners/connection_string.rb +0 -4
  162. data/spec/runners/crud/requirement.rb +40 -3
  163. data/spec/runners/crud/verifier.rb +8 -0
  164. data/spec/runners/transactions/operation.rb +13 -2
  165. data/spec/runners/transactions/test.rb +1 -0
  166. data/spec/runners/unified.rb +96 -0
  167. data/spec/runners/unified/assertions.rb +249 -0
  168. data/spec/runners/unified/change_stream_operations.rb +26 -0
  169. data/spec/runners/unified/crud_operations.rb +199 -0
  170. data/spec/runners/unified/ddl_operations.rb +96 -0
  171. data/spec/runners/unified/entity_map.rb +39 -0
  172. data/spec/runners/unified/error.rb +25 -0
  173. data/spec/runners/unified/event_subscriber.rb +91 -0
  174. data/spec/runners/unified/exceptions.rb +21 -0
  175. data/spec/runners/unified/grid_fs_operations.rb +55 -0
  176. data/spec/runners/unified/support_operations.rb +250 -0
  177. data/spec/runners/unified/test.rb +393 -0
  178. data/spec/runners/unified/test_group.rb +28 -0
  179. data/spec/runners/unified/using_hash.rb +31 -0
  180. data/spec/shared/bin/get-mongodb-download-url +17 -0
  181. data/spec/shared/lib/mrss/cluster_config.rb +218 -0
  182. data/spec/shared/lib/mrss/constraints.rb +43 -0
  183. data/spec/shared/lib/mrss/docker_runner.rb +262 -0
  184. data/spec/shared/lib/mrss/server_version_registry.rb +112 -0
  185. data/spec/shared/lib/mrss/utils.rb +15 -0
  186. data/spec/shared/share/Dockerfile.erb +231 -0
  187. data/spec/shared/shlib/distro.sh +73 -0
  188. data/spec/shared/shlib/server.sh +290 -0
  189. data/spec/shared/shlib/set_env.sh +128 -0
  190. data/spec/solo/clean_exit_spec.rb +21 -0
  191. data/spec/spec_helper.rb +4 -1
  192. data/spec/spec_tests/crud_unified_spec.rb +10 -0
  193. data/spec/spec_tests/data/change_streams/change-streams.yml +0 -1
  194. data/spec/spec_tests/data/crud_unified/estimatedDocumentCount.yml +267 -0
  195. data/spec/spec_tests/data/retryable_reads/estimatedDocumentCount-4.9.yml +60 -0
  196. data/spec/spec_tests/data/retryable_reads/{estimatedDocumentCount.yml → estimatedDocumentCount-pre4.9.yml} +2 -0
  197. data/spec/spec_tests/data/retryable_reads/estimatedDocumentCount-serverErrors-4.9.yml +146 -0
  198. data/spec/spec_tests/data/retryable_reads/{estimatedDocumentCount-serverErrors.yml → estimatedDocumentCount-serverErrors-pre4.9.yml} +2 -0
  199. data/spec/spec_tests/data/retryable_reads/listIndexNames.yml +1 -1
  200. data/spec/spec_tests/data/unified/valid-fail/operation-failure.yml +31 -0
  201. data/spec/spec_tests/data/unified/valid-pass/poc-change-streams.yml +220 -0
  202. data/spec/spec_tests/data/unified/valid-pass/poc-command-monitoring.yml +102 -0
  203. data/spec/spec_tests/data/unified/valid-pass/poc-crud.yml +184 -0
  204. data/spec/spec_tests/data/unified/valid-pass/poc-gridfs.yml +155 -0
  205. data/spec/spec_tests/data/unified/valid-pass/poc-retryable-reads.yml +193 -0
  206. data/spec/spec_tests/data/unified/valid-pass/poc-retryable-writes.yml +210 -0
  207. data/spec/spec_tests/data/unified/valid-pass/poc-sessions.yml +215 -0
  208. data/spec/spec_tests/data/unified/valid-pass/poc-transactions-convenient-api.yml +235 -0
  209. data/spec/spec_tests/data/unified/valid-pass/poc-transactions-mongos-pin-auto.yml +169 -0
  210. data/spec/spec_tests/data/unified/valid-pass/poc-transactions.yml +170 -0
  211. data/spec/spec_tests/data/uri_options/compression-options.yml +1 -1
  212. data/spec/spec_tests/data/versioned_api/crud-api-version-1-strict.yml +416 -0
  213. data/spec/spec_tests/data/versioned_api/crud-api-version-1.yml +409 -0
  214. data/spec/spec_tests/data/versioned_api/runcommand-helper-no-api-version-declared.yml +67 -0
  215. data/spec/spec_tests/data/versioned_api/test-commands-deprecation-errors.yml +47 -0
  216. data/spec/spec_tests/data/versioned_api/test-commands-strict-mode.yml +44 -0
  217. data/spec/spec_tests/data/versioned_api/transaction-handling.yml +180 -0
  218. data/spec/spec_tests/unified_spec.rb +15 -0
  219. data/spec/spec_tests/uri_options_spec.rb +16 -0
  220. data/spec/spec_tests/versioned_api_spec.rb +10 -0
  221. data/spec/support/common_shortcuts.rb +15 -1
  222. data/spec/support/shared/session.rb +2 -2
  223. data/spec/support/spec_config.rb +46 -3
  224. data/spec/support/spec_setup.rb +48 -38
  225. data/spec/support/utils.rb +64 -3
  226. metadata +1104 -992
  227. metadata.gz.sig +0 -0
  228. data/lib/mongo/operation/shared/collections_info_or_list_collections.rb +0 -58
  229. data/lib/mongo/operation/shared/op_msg_or_list_indexes_command.rb +0 -47
  230. data/spec/support/cluster_config.rb +0 -207
@@ -274,27 +274,11 @@ describe Mongo::Cluster do
274
274
 
275
275
  describe '#add' do
276
276
 
277
- context 'when topology is Single' do
278
-
279
- let(:cluster) { cluster_with_semaphore }
280
-
281
- let(:topology) do
282
- Mongo::Cluster::Topology::Single.new({}, cluster)
283
- end
284
-
285
- before do
286
- cluster.add('a')
287
- end
288
-
289
- it 'does not add discovered servers to the cluster' do
290
- expect(cluster.servers[0].address.seed).to_not eq('a')
291
- end
292
- end
293
-
294
277
  context 'topology is Sharded' do
278
+ require_topology :sharded
295
279
 
296
280
  let(:topology) do
297
- Mongo::Cluster::Topology::Single.new({}, cluster)
281
+ Mongo::Cluster::Topology::Sharded.new({}, cluster)
298
282
  end
299
283
 
300
284
  before do
@@ -368,6 +352,7 @@ describe Mongo::Cluster do
368
352
  end
369
353
 
370
354
  before do
355
+ cluster.next_primary
371
356
  cluster.servers.each do |server|
372
357
  expect(server).to receive(:reconnect!).and_call_original
373
358
  end
@@ -82,7 +82,7 @@ describe Mongo::Collection::View::Explainable do
82
82
  it 'triggers server error' do
83
83
  lambda do
84
84
  explain
85
- end.should raise_error(Mongo::Error::OperationFailure, /verbosity string must be/)
85
+ end.should raise_error(Mongo::Error::OperationFailure, /verbosity string must be|value .* for field .*verbosity.* is not a valid value/)
86
86
  end
87
87
  end
88
88
 
@@ -604,36 +604,50 @@ describe Mongo::Collection::View::Readable do
604
604
 
605
605
  describe "#estimated_document_count" do
606
606
 
607
- let(:documents) do
608
- (1..10).map{ |i| { field: "test#{i}" }}
609
- end
610
-
611
- before do
612
- authorized_collection.delete_many
613
- authorized_collection.insert_many(documents)
614
- end
615
-
616
607
  let(:result) do
617
608
  view.estimated_document_count(options)
618
609
  end
619
610
 
620
- context 'when a selector is provided' do
611
+ context 'when collection has documents' do
612
+ let(:documents) do
613
+ (1..10).map{ |i| { field: "test#{i}" }}
614
+ end
621
615
 
622
- let(:selector) do
623
- { field: 'test1' }
616
+ before do
617
+ authorized_collection.delete_many
618
+ authorized_collection.insert_many(documents)
624
619
  end
625
620
 
626
- it 'raises an error' do
627
- expect {
628
- result
629
- }.to raise_error(ArgumentError, "Cannot call estimated_document_count when querying with a filter")
621
+ context 'when a selector is provided' do
622
+
623
+ let(:selector) do
624
+ { field: 'test1' }
625
+ end
626
+
627
+ it 'raises an error' do
628
+ expect {
629
+ result
630
+ }.to raise_error(ArgumentError, "Cannot call estimated_document_count when querying with a filter")
631
+ end
632
+ end
633
+
634
+ context 'when no selector is provided' do
635
+
636
+ it 'returns the estimated count of matching documents' do
637
+ expect(view.estimated_document_count).to eq(10)
638
+ end
630
639
  end
631
640
  end
632
641
 
633
- context 'when no selector is provided' do
642
+ context 'when collection does not exist' do
643
+
644
+ let(:view) do
645
+ Mongo::Collection::View.new(
646
+ authorized_client['nonexistent-collection-for-estimated-document-count'], selector, options)
647
+ end
634
648
 
635
- it 'returns the estimated count of matching documents' do
636
- expect(view.estimated_document_count).to eq(10)
649
+ it 'returns 0' do
650
+ view.estimated_document_count.should == 0
637
651
  end
638
652
  end
639
653
  end
@@ -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