mongo 2.9.2 → 2.10.0.rc0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (227) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data.tar.gz.sig +0 -0
  4. data/lib/mongo.rb +1 -0
  5. data/lib/mongo/auth/user/view.rb +4 -4
  6. data/lib/mongo/bulk_write.rb +14 -8
  7. data/lib/mongo/bulk_write/result.rb +1 -1
  8. data/lib/mongo/bulk_write/result_combiner.rb +2 -2
  9. data/lib/mongo/bulk_write/transformable.rb +17 -9
  10. data/lib/mongo/client.rb +107 -16
  11. data/lib/mongo/cluster.rb +47 -25
  12. data/lib/mongo/cluster/topology/replica_set_no_primary.rb +1 -1
  13. data/lib/mongo/cluster_time.rb +139 -0
  14. data/lib/mongo/collection.rb +84 -25
  15. data/lib/mongo/collection/view.rb +7 -3
  16. data/lib/mongo/collection/view/aggregation.rb +4 -4
  17. data/lib/mongo/collection/view/builder/aggregation.rb +31 -6
  18. data/lib/mongo/collection/view/builder/find_command.rb +4 -1
  19. data/lib/mongo/collection/view/builder/map_reduce.rb +4 -1
  20. data/lib/mongo/collection/view/change_stream.rb +54 -66
  21. data/lib/mongo/collection/view/iterable.rb +2 -2
  22. data/lib/mongo/collection/view/map_reduce.rb +6 -4
  23. data/lib/mongo/collection/view/readable.rb +36 -16
  24. data/lib/mongo/collection/view/writable.rb +68 -22
  25. data/lib/mongo/cursor.rb +87 -20
  26. data/lib/mongo/database.rb +47 -43
  27. data/lib/mongo/database/view.rb +54 -11
  28. data/lib/mongo/error.rb +13 -4
  29. data/lib/mongo/error/invalid_write_concern.rb +2 -2
  30. data/lib/mongo/error/operation_failure.rb +65 -11
  31. data/lib/mongo/error/parser.rb +41 -8
  32. data/lib/mongo/grid/fs_bucket.rb +26 -6
  33. data/lib/mongo/grid/stream/read.rb +9 -2
  34. data/lib/mongo/grid/stream/write.rb +21 -5
  35. data/lib/mongo/index/view.rb +3 -3
  36. data/lib/mongo/lint.rb +10 -3
  37. data/lib/mongo/operation.rb +2 -0
  38. data/lib/mongo/operation/aggregate/result.rb +19 -6
  39. data/lib/mongo/operation/collections_info.rb +1 -1
  40. data/lib/mongo/operation/get_more/result.rb +9 -0
  41. data/lib/mongo/operation/list_collections/command.rb +1 -3
  42. data/lib/mongo/operation/list_collections/op_msg.rb +1 -2
  43. data/lib/mongo/operation/parallel_scan/command.rb +4 -1
  44. data/lib/mongo/operation/parallel_scan/op_msg.rb +4 -1
  45. data/lib/mongo/operation/result.rb +27 -4
  46. data/lib/mongo/operation/shared/executable.rb +19 -5
  47. data/lib/mongo/operation/shared/executable_no_validate.rb +1 -2
  48. data/lib/mongo/operation/shared/executable_transaction_label.rb +0 -9
  49. data/lib/mongo/operation/shared/polymorphic_result.rb +9 -1
  50. data/lib/mongo/operation/shared/result/aggregatable.rb +2 -2
  51. data/lib/mongo/operation/shared/sessions_supported.rb +42 -32
  52. data/lib/mongo/operation/shared/specifiable.rb +40 -0
  53. data/lib/mongo/operation/shared/unpinnable.rb +39 -0
  54. data/lib/mongo/operation/shared/write.rb +1 -1
  55. data/lib/mongo/protocol/update.rb +6 -2
  56. data/lib/mongo/retryable.rb +79 -39
  57. data/lib/mongo/server/connection.rb +10 -3
  58. data/lib/mongo/server/description.rb +25 -1
  59. data/lib/mongo/server/monitor/connection.rb +1 -1
  60. data/lib/mongo/server_selector.rb +10 -0
  61. data/lib/mongo/server_selector/selectable.rb +172 -32
  62. data/lib/mongo/session.rb +654 -581
  63. data/lib/mongo/session/session_pool.rb +1 -1
  64. data/lib/mongo/socket.rb +7 -28
  65. data/lib/mongo/socket/ssl.rb +26 -1
  66. data/lib/mongo/socket/tcp.rb +3 -0
  67. data/lib/mongo/socket/unix.rb +3 -0
  68. data/lib/mongo/uri.rb +112 -265
  69. data/lib/mongo/uri/srv_protocol.rb +4 -1
  70. data/lib/mongo/version.rb +1 -1
  71. data/lib/mongo/write_concern.rb +10 -29
  72. data/lib/mongo/write_concern/acknowledged.rb +12 -0
  73. data/lib/mongo/write_concern/base.rb +17 -13
  74. data/lib/mongo/write_concern/unacknowledged.rb +12 -0
  75. data/spec/atlas/atlas_connectivity_spec.rb +7 -37
  76. data/spec/atlas/operations_spec.rb +25 -0
  77. data/spec/integration/change_stream_examples_spec.rb +45 -31
  78. data/spec/integration/change_stream_spec.rb +305 -5
  79. data/spec/integration/client_spec.rb +44 -0
  80. data/spec/integration/command_monitoring_spec.rb +1 -0
  81. data/spec/integration/command_spec.rb +7 -1
  82. data/spec/integration/mmapv1_spec.rb +28 -0
  83. data/spec/integration/mongos_pinning_spec.rb +34 -0
  84. data/spec/integration/operation_failure_code_spec.rb +2 -2
  85. data/spec/integration/{read_concern.rb → read_concern_spec.rb} +7 -1
  86. data/spec/integration/read_preference_spec.rb +485 -0
  87. data/spec/integration/retryable_writes_spec.rb +8 -19
  88. data/spec/integration/sdam_error_handling_spec.rb +1 -1
  89. data/spec/integration/sdam_events_spec.rb +2 -2
  90. data/spec/integration/server_description_spec.rb +14 -17
  91. data/spec/integration/server_selector_spec.rb +7 -3
  92. data/spec/integration/server_spec.rb +48 -0
  93. data/spec/integration/ssl_uri_options_spec.rb +1 -1
  94. data/spec/integration/step_down_spec.rb +10 -4
  95. data/spec/integration/transactions_examples_spec.rb +11 -10
  96. data/spec/lite_spec_helper.rb +19 -16
  97. data/spec/mongo/auth/scram/negotiation_spec.rb +11 -8
  98. data/spec/mongo/bulk_write/ordered_combiner_spec.rb +6 -6
  99. data/spec/mongo/bulk_write/unordered_combiner_spec.rb +4 -4
  100. data/spec/mongo/bulk_write_spec.rb +12 -2
  101. data/spec/mongo/client_construction_spec.rb +160 -8
  102. data/spec/mongo/client_spec.rb +5 -4
  103. data/spec/mongo/cluster_spec.rb +6 -6
  104. data/spec/mongo/cluster_time_spec.rb +148 -0
  105. data/spec/mongo/collection/view/aggregation_spec.rb +34 -15
  106. data/spec/mongo/collection/view/change_stream_spec.rb +62 -3
  107. data/spec/mongo/collection/view/map_reduce_spec.rb +7 -5
  108. data/spec/mongo/collection/view/readable_spec.rb +4 -4
  109. data/spec/mongo/collection_spec.rb +331 -14
  110. data/spec/mongo/cursor_spec.rb +117 -5
  111. data/spec/mongo/database_spec.rb +240 -8
  112. data/spec/mongo/error/operation_failure_spec.rb +47 -1
  113. data/spec/mongo/error/parser_spec.rb +160 -23
  114. data/spec/mongo/operation/insert/bulk_spec.rb +2 -1
  115. data/spec/mongo/operation/result_spec.rb +27 -0
  116. data/spec/mongo/operation/update/bulk_spec.rb +1 -0
  117. data/spec/mongo/retryable_spec.rb +2 -0
  118. data/spec/mongo/server/app_metadata_spec.rb +2 -2
  119. data/spec/mongo/server/connection_spec.rb +13 -17
  120. data/spec/mongo/server/monitor/connection_spec.rb +13 -10
  121. data/spec/mongo/server_selector_spec.rb +34 -2
  122. data/spec/mongo/session/session_pool_spec.rb +14 -3
  123. data/spec/mongo/session_spec.rb +3 -3
  124. data/spec/mongo/session_transaction_spec.rb +4 -3
  125. data/spec/mongo/socket/ssl_spec.rb +19 -5
  126. data/spec/mongo/socket_spec.rb +1 -62
  127. data/spec/mongo/uri/srv_protocol_spec.rb +14 -20
  128. data/spec/mongo/uri_option_parsing_spec.rb +94 -8
  129. data/spec/mongo/uri_spec.rb +23 -10
  130. data/spec/mongo/write_concern_spec.rb +56 -3
  131. data/spec/spec_tests/change_streams_spec.rb +2 -1
  132. data/spec/spec_tests/cmap_spec.rb +1 -1
  133. data/spec/spec_tests/crud_spec.rb +12 -2
  134. data/spec/spec_tests/data/change_streams/change-streams-errors.yml +24 -1
  135. data/spec/spec_tests/data/change_streams/change-streams.yml +172 -3
  136. data/spec/spec_tests/data/command_monitoring/bulkWrite.yml +1 -1
  137. data/spec/spec_tests/data/command_monitoring/updateMany.yml +0 -2
  138. data/spec/spec_tests/data/command_monitoring/updateOne.yml +0 -5
  139. data/spec/spec_tests/data/crud/read/aggregate-out.yml +0 -6
  140. data/spec/spec_tests/data/crud/read/count-empty.yml +29 -0
  141. data/spec/spec_tests/data/crud/write/bulkWrite-arrayFilters.yml +1 -0
  142. data/spec/spec_tests/data/crud/write/bulkWrite-collation.yml +101 -0
  143. data/spec/spec_tests/data/crud/write/bulkWrite.yml +401 -0
  144. data/spec/spec_tests/data/crud/write/insertMany.yml +58 -2
  145. data/spec/spec_tests/data/crud/write/updateMany-arrayFilters.yml +3 -0
  146. data/spec/spec_tests/data/crud/write/updateOne-arrayFilters.yml +6 -1
  147. data/spec/spec_tests/data/crud_v2/aggregate-merge.yml +103 -0
  148. data/spec/spec_tests/data/crud_v2/aggregate-out-readConcern.yml +110 -0
  149. data/spec/spec_tests/data/crud_v2/bulkWrite-arrayFilters.yml +81 -0
  150. data/spec/spec_tests/data/crud_v2/db-aggregate.yml +38 -0
  151. data/spec/spec_tests/data/crud_v2/updateWithPipelines.yml +92 -0
  152. data/spec/spec_tests/data/retryable_writes/insertOne-serverErrors.yml +2 -2
  153. data/spec/spec_tests/data/transactions/abort.yml +3 -0
  154. data/spec/spec_tests/data/transactions/bulk.yml +3 -8
  155. data/spec/spec_tests/data/transactions/causal-consistency.yml +3 -8
  156. data/spec/spec_tests/data/transactions/commit.yml +3 -1
  157. data/spec/spec_tests/data/transactions/count.yml +3 -0
  158. data/spec/spec_tests/data/transactions/delete.yml +3 -0
  159. data/spec/spec_tests/data/transactions/error-labels.yml +4 -1
  160. data/spec/spec_tests/data/transactions/errors-client.yml +56 -0
  161. data/spec/spec_tests/data/transactions/errors.yml +3 -0
  162. data/spec/spec_tests/data/transactions/findOneAndDelete.yml +3 -0
  163. data/spec/spec_tests/data/transactions/findOneAndReplace.yml +3 -0
  164. data/spec/spec_tests/data/transactions/findOneAndUpdate.yml +3 -0
  165. data/spec/spec_tests/data/transactions/insert.yml +3 -0
  166. data/spec/spec_tests/data/transactions/isolation.yml +3 -0
  167. data/spec/spec_tests/data/transactions/mongos-pin-auto.yml +1671 -0
  168. data/spec/spec_tests/data/transactions/mongos-recovery-token.yml +347 -0
  169. data/spec/spec_tests/data/transactions/pin-mongos.yml +557 -0
  170. data/spec/spec_tests/data/transactions/read-concern.yml +3 -0
  171. data/spec/spec_tests/data/transactions/read-pref.yml +3 -0
  172. data/spec/spec_tests/data/transactions/reads.yml +3 -0
  173. data/spec/spec_tests/data/transactions/retryable-abort.yml +5 -2
  174. data/spec/spec_tests/data/transactions/retryable-commit.yml +4 -1
  175. data/spec/spec_tests/data/transactions/retryable-writes.yml +3 -0
  176. data/spec/spec_tests/data/transactions/run-command.yml +3 -0
  177. data/spec/spec_tests/data/transactions/transaction-options.yml +6 -0
  178. data/spec/spec_tests/data/transactions/update.yml +3 -8
  179. data/spec/spec_tests/data/transactions/write-concern.yml +348 -38
  180. data/spec/spec_tests/data/transactions_api/callback-aborts.yml +6 -0
  181. data/spec/spec_tests/data/transactions_api/callback-commits.yml +5 -0
  182. data/spec/spec_tests/data/transactions_api/callback-retry.yml +7 -2
  183. data/spec/spec_tests/data/transactions_api/commit-retry.yml +70 -15
  184. data/spec/spec_tests/data/transactions_api/commit-transienttransactionerror-4.2.yml +3 -0
  185. data/spec/spec_tests/data/transactions_api/commit-transienttransactionerror.yml +3 -0
  186. data/spec/spec_tests/data/transactions_api/commit-writeconcernerror.yml +59 -109
  187. data/spec/spec_tests/data/transactions_api/commit.yml +5 -0
  188. data/spec/spec_tests/data/transactions_api/transaction-options.yml +10 -0
  189. data/spec/spec_tests/retryable_reads_spec.rb +5 -2
  190. data/spec/spec_tests/retryable_writes_spec.rb +5 -2
  191. data/spec/spec_tests/sdam_monitoring_spec.rb +3 -3
  192. data/spec/spec_tests/sdam_spec.rb +2 -2
  193. data/spec/spec_tests/transactions_api_spec.rb +1 -67
  194. data/spec/spec_tests/transactions_spec.rb +2 -66
  195. data/spec/support/authorization.rb +4 -0
  196. data/spec/support/change_streams.rb +30 -10
  197. data/spec/support/change_streams/operation.rb +27 -0
  198. data/spec/support/client_registry.rb +44 -25
  199. data/spec/support/cluster_config.rb +25 -14
  200. data/spec/support/cluster_tools.rb +32 -10
  201. data/spec/support/command_monitoring.rb +1 -1
  202. data/spec/support/common_shortcuts.rb +30 -0
  203. data/spec/support/connection_string.rb +8 -3
  204. data/spec/support/constraints.rb +34 -0
  205. data/spec/support/crud.rb +31 -16
  206. data/spec/support/crud/context.rb +23 -0
  207. data/spec/support/crud/operation.rb +311 -14
  208. data/spec/support/crud/spec.rb +2 -1
  209. data/spec/support/crud/test.rb +24 -27
  210. data/spec/support/crud/test_base.rb +22 -0
  211. data/spec/support/crud/verifier.rb +15 -1
  212. data/spec/support/event_subscriber.rb +12 -0
  213. data/spec/support/sdam_formatter_integration.rb +12 -6
  214. data/spec/support/shared/server_selector.rb +10 -0
  215. data/spec/support/shared/session.rb +13 -12
  216. data/spec/support/spec_config.rb +32 -22
  217. data/spec/support/spec_setup.rb +2 -2
  218. data/spec/support/transactions.rb +87 -0
  219. data/spec/support/transactions/context.rb +33 -0
  220. data/spec/support/transactions/operation.rb +99 -349
  221. data/spec/support/transactions/spec.rb +1 -3
  222. data/spec/support/transactions/test.rb +110 -49
  223. data/spec/support/utils.rb +74 -1
  224. metadata +52 -10
  225. metadata.gz.sig +0 -0
  226. data/spec/support/crud/read.rb +0 -265
  227. data/spec/support/crud/write.rb +0 -284
@@ -74,20 +74,6 @@ class ClusterConfig
74
74
  end
75
75
  end
76
76
 
77
- def primary_address_str
78
- primary_address
79
- end
80
-
81
- def primary_address_host
82
- both = primary_address_str
83
- both.split(':').first
84
- end
85
-
86
- def primary_address_port
87
- both = primary_address_str
88
- both.split(':')[1] || 27017
89
- end
90
-
91
77
  # Try running a command on the admin database to see if the mongod was
92
78
  # started with auth.
93
79
  def auth_enabled?
@@ -111,4 +97,29 @@ class ClusterConfig
111
97
  topology.to_sym
112
98
  end
113
99
  end
100
+
101
+ def storage_engine
102
+ @storage_engine ||= begin
103
+ # 2.6 does not have wired tiger
104
+ if short_server_version == '2.6'
105
+ :mmapv1
106
+ else
107
+ client = ClientRegistry.instance.global_client('root_authorized')
108
+ if topology == :sharded
109
+ shards = client.use(:admin).command(listShards: 1).first
110
+ shard = shards['shards'].first
111
+ address_str = shard['host'].sub(/^.*\//, '').sub(/,.*/, '')
112
+ client = ClusterTools.instance.direct_client(address_str,
113
+ SpecConfig.instance.test_options.merge(SpecConfig.instance.auth_options).merge(connect: :direct))
114
+ end
115
+ rv = client.use(:admin).command(serverStatus: 1).first
116
+ rv = rv['storageEngine']['name']
117
+ rv_map = {
118
+ 'wiredTiger' => :wired_tiger,
119
+ 'mmapv1' => :mmapv1,
120
+ }
121
+ rv_map[rv] || rv
122
+ end
123
+ end
124
+ end
114
125
  end
@@ -309,25 +309,47 @@ class ClusterTools
309
309
  end
310
310
  end
311
311
 
312
- private
313
-
314
312
  def admin_client
315
313
  # Since we are triggering elections, we need to have a higher server
316
314
  # selection timeout applied. The default timeout for tests assumes a
317
315
  # stable deployment.
318
- @admin_client ||= ClientRegistry.instance.global_client('root_authorized_admin').
319
- with(server_selection_timeout: 15)
316
+ (
317
+ @admin_client ||= ClientRegistry.instance.global_client('root_authorized').
318
+ with(server_selection_timeout: 15).use(:admin)
319
+ ).tap do |client|
320
+ ClientRegistry.reconnect_client_if_perished(client)
321
+ end
320
322
  end
321
323
 
322
- def direct_client(address)
324
+ def direct_client(address, options = {})
323
325
  @direct_clients ||= {}
324
- @direct_clients[address] ||= ClientRegistry.instance.new_local_client(
325
- [address.to_s],
326
- SpecConfig.instance.test_options.merge(
327
- SpecConfig.instance.auth_options).merge(
328
- connect: :direct, server_selection_timeout: 10))
326
+ cache_key = {address: address}.update(options)
327
+ (
328
+ @direct_clients[cache_key] ||= ClientRegistry.instance.new_local_client(
329
+ [address.to_s],
330
+ SpecConfig.instance.test_options.merge(
331
+ SpecConfig.instance.auth_options).merge(
332
+ connect: :direct, server_selection_timeout: 10).merge(options))
333
+ ).tap do |client|
334
+ ClientRegistry.reconnect_client_if_perished(client)
335
+ end
329
336
  end
330
337
 
338
+ def close_clients
339
+ if @admin_client
340
+ @admin_client.close
341
+ @admin_client = nil
342
+ end
343
+ if @direct_clients
344
+ @direct_clients.each do |cache_key, client|
345
+ client.close
346
+ end
347
+ @direct_clients = nil
348
+ end
349
+ end
350
+
351
+ private
352
+
331
353
  def each_server(&block)
332
354
  admin_client.cluster.servers_list.each(&block)
333
355
  end
@@ -248,7 +248,7 @@ module Mongo
248
248
  @description = test['description']
249
249
  @max_server_version = test['ignore_if_server_version_greater_than']
250
250
  @min_server_fcv = test['ignore_if_server_version_less_than']
251
- @operation = Mongo::CRUD::Operation.get(test['operation'])
251
+ @operation = Mongo::CRUD::Operation.new(self, test['operation'])
252
252
  @expectations = test['expectations'].map{ |e| Expectation.new(e) }
253
253
  end
254
254
 
@@ -9,6 +9,36 @@ module CommonShortcuts
9
9
  end
10
10
  end
11
11
  end
12
+
13
+ # For tests which require clients to connect, clean slate asks all
14
+ # existing clients to be closed prior to the test execution.
15
+ # Note that clean_slate closes all clients for each test in the scope.
16
+ def clean_slate
17
+ before do
18
+ ClientRegistry.instance.close_all_clients
19
+ end
20
+ end
21
+
22
+ # Similar to clean slate but closes clients once before all tests in
23
+ # the scope. Use when the tests do not create new clients but do not
24
+ # want any background output from previously existing clients.
25
+ def clean_slate_for_all
26
+ before(:all) do
27
+ ClientRegistry.instance.close_all_clients
28
+ end
29
+ end
30
+
31
+ # For some reason, there are tests which fail on evergreen either
32
+ # intermittently or reliably that always succeed locally.
33
+ # Debugging of tests in evergreen is difficult/impossible,
34
+ # thus this workaround.
35
+ def clean_slate_on_evergreen
36
+ before(:all) do
37
+ if SpecConfig.instance.ci?
38
+ ClientRegistry.instance.close_all_clients
39
+ end
40
+ end
41
+ end
12
42
  end
13
43
 
14
44
  module InstanceMethods
@@ -123,6 +123,7 @@ module Mongo
123
123
  end
124
124
 
125
125
  class Test
126
+ include RSpec::Core::Pending
126
127
 
127
128
  attr_reader :description
128
129
  attr_reader :uri_string
@@ -153,6 +154,10 @@ module Mongo
153
154
 
154
155
  def client
155
156
  @client ||= ClientRegistry.instance.new_local_client(@spec['uri'], monitoring_io: false)
157
+ rescue Mongo::Error::LintError => e
158
+ if e.message =~ /arbitraryButStillValid/
159
+ skip 'Test uses a read concern that fails linter'
160
+ end
156
161
  end
157
162
 
158
163
  def uri
@@ -223,9 +228,9 @@ module Mongo
223
228
  'maxidletimems' => :max_idle_time,
224
229
 
225
230
  # Write Options
226
- 'journal' => [:write, 'j'],
227
- 'w' => [:write, 'w'],
228
- 'wtimeoutms' => [:write, 'wtimeout'],
231
+ 'journal' => [:write_concern, 'j'],
232
+ 'w' => [:write_concern, 'w'],
233
+ 'wtimeoutms' => [:write_concern, 'wtimeout'],
229
234
 
230
235
  # Read Options
231
236
  'readpreference' => ['read', 'mode'],
@@ -157,4 +157,38 @@ module Constraints
157
157
  end
158
158
  end
159
159
  end
160
+
161
+ def require_no_multi_shard
162
+ before do
163
+ if ClusterConfig.instance.topology == :sharded && SpecConfig.instance.addresses.length > 1
164
+ skip 'Test requires a single shard if run in sharded topology'
165
+ end
166
+ end
167
+ end
168
+
169
+ def require_wired_tiger
170
+ before(:all) do
171
+ if ClusterConfig.instance.storage_engine != :wired_tiger
172
+ skip 'Test requires WiredTiger storage engine'
173
+ end
174
+ end
175
+ end
176
+
177
+ def require_wired_tiger_on_36
178
+ before(:all) do
179
+ if ClusterConfig.instance.short_server_version >= '3.6'
180
+ if ClusterConfig.instance.storage_engine != :wired_tiger
181
+ skip 'Test requires WiredTiger storage engine on 3.6+ servers'
182
+ end
183
+ end
184
+ end
185
+ end
186
+
187
+ def require_mmapv1
188
+ before do
189
+ if ClusterConfig.instance.storage_engine != :mmapv1
190
+ skip 'Test requires MMAPv1 storage engine'
191
+ end
192
+ end
193
+ end
160
194
  end
@@ -15,13 +15,17 @@
15
15
  require 'support/gridfs'
16
16
  require 'support/crud/requirement'
17
17
  require 'support/crud/spec'
18
+ require 'support/crud/test_base'
18
19
  require 'support/crud/test'
19
20
  require 'support/crud/outcome'
21
+ require 'support/crud/context'
20
22
  require 'support/crud/operation'
21
- require 'support/crud/read'
22
- require 'support/crud/write'
23
23
  require 'support/crud/verifier'
24
24
 
25
+ def collection_data(collection)
26
+ collection.find.to_a
27
+ end
28
+
25
29
  def crud_execute_operations(spec, test, num_ops, event_subscriber, expect_error,
26
30
  client
27
31
  )
@@ -42,7 +46,7 @@ def crud_execute_operations(spec, test, num_ops, event_subscriber, expect_error,
42
46
  result = if expect_error.nil?
43
47
  res = nil
44
48
  begin
45
- res = test.run(spec, client, num_ops)
49
+ res = test.run(client, num_ops)
46
50
  rescue => e
47
51
  res = e
48
52
  end
@@ -50,13 +54,13 @@ def crud_execute_operations(spec, test, num_ops, event_subscriber, expect_error,
50
54
  elsif expect_error
51
55
  error = nil
52
56
  begin
53
- test.run(spec, client, num_ops)
57
+ test.run(client, num_ops)
54
58
  rescue => e
55
59
  error = e
56
60
  end
57
61
  error
58
62
  else
59
- test.run(spec, client, num_ops)
63
+ test.run(client, num_ops)
60
64
  end
61
65
 
62
66
  $crud_event_cache ||= {}
@@ -68,7 +72,7 @@ def crud_execute_operations(spec, test, num_ops, event_subscriber, expect_error,
68
72
  if last_op.outcome && last_op.outcome.collection_data?
69
73
  verify_collection = client[last_op.verify_collection_name]
70
74
  $crud_collection_data_cache ||= {}
71
- $crud_collection_data_cache[cache_key] = verify_collection.find.to_a
75
+ $crud_collection_data_cache[cache_key] = collection_data(verify_collection)
72
76
  end
73
77
 
74
78
  result
@@ -136,7 +140,7 @@ def define_crud_spec_test_examples(spec, req = nil, &block)
136
140
  result
137
141
  verifier.verify_collection_data(
138
142
  operation.outcome.collection_data,
139
- verify_collection.find.to_a)
143
+ collection_data(verify_collection))
140
144
  end
141
145
  end
142
146
 
@@ -173,6 +177,20 @@ def define_crud_spec_test_examples(spec, req = nil, &block)
173
177
  end
174
178
  end
175
179
  end
180
+
181
+ if test.outcome && test.outcome.collection_data?
182
+ let(:result) do
183
+ crud_execute_operations(spec, test, test.operations.length,
184
+ event_subscriber, nil, client)
185
+ end
186
+
187
+ it 'has the correct data in the collection' do
188
+ result
189
+ verifier.verify_collection_data(
190
+ test.outcome.collection_data,
191
+ collection_data(client[test.outcome.collection_name || spec.collection_name]))
192
+ end
193
+ end
176
194
  end
177
195
  end
178
196
  end
@@ -203,17 +221,14 @@ def define_spec_tests_with_requirements(spec, &block)
203
221
  end
204
222
  end
205
223
 
206
- def define_crud_spec_tests(description, test_paths, &block)
207
- describe(description) do
208
-
209
- test_paths.each do |path|
224
+ def define_crud_spec_tests(test_paths, spec_cls = Mongo::CRUD::Spec, &block)
225
+ test_paths.each do |path|
210
226
 
211
- spec = Mongo::CRUD::Spec.new(path)
227
+ spec = spec_cls.new(path)
212
228
 
213
- context(spec.description) do
214
- define_spec_tests_with_requirements(spec) do |req|
215
- define_crud_spec_test_examples(spec, req, &block)
216
- end
229
+ context(spec.description) do
230
+ define_spec_tests_with_requirements(spec) do |req|
231
+ define_crud_spec_test_examples(spec, req, &block)
217
232
  end
218
233
  end
219
234
  end
@@ -0,0 +1,23 @@
1
+ # Copyright (C) 2019 MongoDB, Inc.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ module Mongo
16
+ module CRUD
17
+ class Context
18
+ def transform_arguments(arguments)
19
+ arguments
20
+ end
21
+ end
22
+ end
23
+ end
@@ -15,29 +15,326 @@
15
15
  module Mongo
16
16
  module CRUD
17
17
 
18
- # Helper module for instantiating either a Read or Write test operation.
19
- #
20
- # @since 2.0.0
21
- module Operation
22
- extend self
18
+ class Operation
23
19
 
24
- # Get a new Operation.
25
- #
26
- # @example Get the operation.
27
- # Operation.get(spec)
20
+ # Instantiate the operation.
28
21
  #
29
22
  # @param [ Hash ] spec The operation specification.
23
+ # @param [ Hash ] outcome_spec The outcome specification.
24
+ # If not provided, outcome is taken out of operation specification.
25
+ #
26
+ # @since 2.0.0
27
+ def initialize(crud_test, spec, outcome_spec = nil)
28
+ @crud_test = crud_test
29
+ @spec = IceNine.deep_freeze(spec)
30
+ @name = spec['name']
31
+ @arguments = spec['arguments'] || {}
32
+ @outcome = Outcome.new(outcome_spec || spec)
33
+ end
34
+
35
+ # The operation name.
30
36
  #
31
- # @return [ Operation::Write, Operation::Read ] The Operation object.
37
+ # @return [ String ] name The operation name.
32
38
  #
33
39
  # @since 2.0.0
34
- def get(spec, outcome_spec = nil)
35
- if Write::OPERATIONS.keys.include?(spec['name'])
36
- Write.new(spec, outcome_spec)
40
+ attr_reader :name
41
+
42
+ attr_reader :arguments
43
+
44
+ attr_reader :outcome
45
+
46
+ def object
47
+ @spec['object'] || 'collection'
48
+ end
49
+
50
+ # Which collection to verify results from.
51
+ # Returns the collection name specified on the operation, or
52
+ # the collection name for the entire spec file.
53
+ def verify_collection_name
54
+ if outcome && outcome.collection_name
55
+ outcome.collection_name
37
56
  else
38
- Read.new(spec, outcome_spec)
57
+ @spec['collection_name'] || 'crud_spec_test'
58
+ end
59
+ end
60
+
61
+ # Whether the operation is expected to have results.
62
+ #
63
+ # @example Whether the operation is expected to have results.
64
+ # operation.has_results?
65
+ #
66
+ # @return [ true, false ] If the operation is expected to have results.
67
+ #
68
+ # @since 2.0.0
69
+ def has_results?
70
+ !(name == 'aggregate' &&
71
+ pipeline.find {|op| op.keys.include?('$out') })
72
+ end
73
+
74
+ # Execute the operation.
75
+ #
76
+ # @example Execute the operation.
77
+ # operation.execute
78
+ #
79
+ # @param [ Collection ] collection The collection to execute the operation on.
80
+ #
81
+ # @return [ Result, Array<Hash> ] The result of executing the operation.
82
+ #
83
+ # @since 2.0.0
84
+ def execute(target)
85
+ op_name = Utils.underscore(name)
86
+ if target.is_a?(Mongo::Database)
87
+ op_name = "db_#{op_name}"
88
+ elsif target.is_a?(Mongo::Client)
89
+ op_name= "client_#{op_name}"
90
+ end
91
+ send(op_name, target, Context.new)
92
+ end
93
+
94
+ def collection_options
95
+ Utils.convert_operation_options(@spec['collectionOptions'])
96
+ end
97
+
98
+ private
99
+
100
+ # read operations
101
+
102
+ def aggregate(collection, context)
103
+ collection.aggregate(arguments['pipeline'], context.transform_arguments(options)).to_a
104
+ end
105
+
106
+ def db_aggregate(database, context)
107
+ database.aggregate(arguments['pipeline'], context.transform_arguments(options)).to_a
108
+ end
109
+
110
+ def count(collection, context)
111
+ collection.count(arguments['filter'], context.transform_arguments(options))
112
+ end
113
+
114
+ def count_documents(collection, context)
115
+ collection.count_documents(arguments['filter'], context.transform_arguments(options))
116
+ end
117
+
118
+ def distinct(collection, context)
119
+ collection.distinct(arguments['fieldName'], arguments['filter'], context.transform_arguments(options))
120
+ end
121
+
122
+ def estimated_document_count(collection, context)
123
+ collection.estimated_document_count(context.transform_arguments(options))
124
+ end
125
+
126
+ def find(collection, context)
127
+ opts = context.transform_arguments(options)
128
+ if arguments['modifiers']
129
+ opts = opts.merge(modifiers: BSON::Document.new(arguments['modifiers']))
130
+ end
131
+ if read_preference
132
+ collection = collection.with(read: read_preference)
133
+ end
134
+ collection.find(arguments['filter'], opts).to_a
135
+ end
136
+
137
+ def find_one(collection, context)
138
+ find(collection, context).first
139
+ end
140
+
141
+ def client_list_databases(client, context)
142
+ client.list_databases
143
+ end
144
+
145
+ def client_list_database_names(client, context)
146
+ client.list_databases({}, true)
147
+ end
148
+
149
+ def client_list_database_objects(client, context)
150
+ client.list_mongo_databases
151
+ end
152
+
153
+ def db_list_collections(database, context)
154
+ database.list_collections
155
+ end
156
+
157
+ def db_list_collection_names(database, context)
158
+ database.collection_names
159
+ end
160
+
161
+ def db_list_collection_objects(database, context)
162
+ database.collections
163
+ end
164
+
165
+ def list_indexes(collection, context)
166
+ collection.indexes.to_a
167
+ end
168
+
169
+ def watch(collection, context)
170
+ collection.watch
171
+ end
172
+
173
+ def db_watch(database, context)
174
+ database.watch
175
+ end
176
+
177
+ def client_watch(client, context)
178
+ client.watch
179
+ end
180
+
181
+ def download(fs_bucket, context)
182
+ stream = fs_bucket.open_download_stream(BSON::ObjectId.from_string(arguments['id']['$oid']))
183
+ stream.read
184
+ end
185
+
186
+ def download_by_name(fs_bucket, context)
187
+ stream = fs_bucket.open_download_stream_by_name(arguments['filename'])
188
+ stream.read
189
+ end
190
+
191
+ def map_reduce(collection, context)
192
+ view = Mongo::Collection::View.new(collection)
193
+ mr = Mongo::Collection::View::MapReduce.new(view, arguments['map']['$code'], arguments['reduce']['$code'])
194
+ mr.to_a
195
+ end
196
+
197
+ # write operations
198
+
199
+ def bulk_write(collection, context)
200
+ result = collection.bulk_write(requests, context.transform_arguments(options))
201
+ return_doc = {}
202
+ return_doc['deletedCount'] = result.deleted_count || 0
203
+ return_doc['insertedIds'] = result.inserted_ids if result.inserted_ids
204
+ return_doc['insertedCount'] = result.inserted_count || 0
205
+ return_doc['upsertedId'] = result.upserted_id if arguments['upsert']
206
+ return_doc['upsertedIds'] = result.upserted_ids if result.upserted_ids
207
+ return_doc['upsertedCount'] = result.upserted_count || 0
208
+ return_doc['matchedCount'] = result.matched_count || 0
209
+ return_doc['modifiedCount'] = result.modified_count || 0
210
+ return_doc
211
+ end
212
+
213
+ def delete_many(collection, context)
214
+ result = collection.delete_many(arguments['filter'], context.transform_arguments(options))
215
+ { 'deletedCount' => result.deleted_count }
216
+ end
217
+
218
+ def delete_one(collection, context)
219
+ result = collection.delete_one(arguments['filter'], context.transform_arguments(options))
220
+ { 'deletedCount' => result.deleted_count }
221
+ end
222
+
223
+ def insert_many(collection, context)
224
+ result = collection.insert_many(arguments['documents'], context.transform_arguments(options))
225
+ { 'insertedIds' => result.inserted_ids }
226
+ end
227
+
228
+ def insert_one(collection, context)
229
+ result = collection.insert_one(arguments['document'], context.transform_arguments(options))
230
+ { 'insertedId' => result.inserted_id }
231
+ end
232
+
233
+ def replace_one(collection, context)
234
+ result = collection.replace_one(arguments['filter'], arguments['replacement'], context.transform_arguments(options))
235
+ update_return_doc(result)
236
+ end
237
+
238
+ def update_many(collection, context)
239
+ result = collection.update_many(arguments['filter'], arguments['update'], context.transform_arguments(options))
240
+ update_return_doc(result)
241
+ end
242
+
243
+ def update_one(collection, context)
244
+ result = collection.update_one(arguments['filter'], arguments['update'], context.transform_arguments(options))
245
+ update_return_doc(result)
246
+ end
247
+
248
+ def find_one_and_delete(collection, context)
249
+ collection.find_one_and_delete(arguments['filter'], context.transform_arguments(options))
250
+ end
251
+
252
+ def find_one_and_replace(collection, context)
253
+ collection.find_one_and_replace(arguments['filter'], arguments['replacement'], context.transform_arguments(options))
254
+ end
255
+
256
+ def find_one_and_update(collection, context)
257
+ collection.find_one_and_update(arguments['filter'], arguments['update'], context.transform_arguments(options))
258
+ end
259
+
260
+ # options & arguments
261
+
262
+ def options
263
+ out = {}
264
+ # Most tests have an "arguments" key which is a hash of options to
265
+ # be provided to the operation. The command monitoring unacknowledged
266
+ # bulk write test is an exception in that it has an "options" key
267
+ # with the options.
268
+ arguments.merge(arguments['options'] || {}).each do |spec_k, v|
269
+ ruby_k = Utils.underscore(spec_k).to_sym
270
+
271
+ if v.is_a?(Hash) && v['$numberLong']
272
+ v = v['$numberLong'].to_i
273
+ end
274
+
275
+ if respond_to?("transform_#{ruby_k}", true)
276
+ v = send("transform_#{ruby_k}", v)
277
+ end
278
+
279
+ out[ruby_k] = v
280
+ end
281
+ out
282
+ end
283
+
284
+ def requests
285
+ arguments['requests'].map do |request|
286
+ case request.keys.first
287
+ when 'insertOne' then
288
+ { insert_one: request['insertOne']['document'] }
289
+ when 'updateOne' then
290
+ update = request['updateOne']
291
+ { update_one: { filter: update['filter'], update: update['update'] } }
292
+ when 'name' then
293
+ bulk_request(request)
294
+ end
39
295
  end
40
296
  end
297
+
298
+ def bulk_request(request)
299
+ op_name = Utils.underscore(request['name'])
300
+ args = Utils.shallow_snakeize_hash(request['arguments'])
301
+ if args[:document]
302
+ unless args.keys == [:document]
303
+ raise "If :document is given, it must be the only key"
304
+ end
305
+ args = args[:document]
306
+ end
307
+ { op_name => args }
308
+ end
309
+
310
+ def upsert
311
+ arguments['upsert']
312
+ end
313
+
314
+ def transform_return_document(v)
315
+ Utils.underscore(v).to_sym
316
+ end
317
+
318
+ def update
319
+ arguments['update']
320
+ end
321
+
322
+ def transform_read_preference(v)
323
+ Utils.snakeize_hash(v)
324
+ end
325
+
326
+ def read_preference
327
+ transform_read_preference(@spec['read_preference'])
328
+ end
329
+
330
+ def update_return_doc(result)
331
+ return_doc = {}
332
+ return_doc['upsertedId'] = result.upserted_id if arguments['upsert']
333
+ return_doc['upsertedCount'] = result.upserted_count
334
+ return_doc['matchedCount'] = result.matched_count
335
+ return_doc['modifiedCount'] = result.modified_count if result.modified_count
336
+ return_doc
337
+ end
41
338
  end
42
339
  end
43
340
  end