mongo 2.16.1 → 2.17.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (96) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/README.md +1 -1
  4. data/lib/mongo/auth/aws/request.rb +0 -1
  5. data/lib/mongo/client.rb +4 -0
  6. data/lib/mongo/collection/view/aggregation.rb +62 -17
  7. data/lib/mongo/collection/view/builder/aggregation.rb +11 -13
  8. data/lib/mongo/collection/view/builder/map_reduce.rb +1 -1
  9. data/lib/mongo/collection/view/change_stream.rb +7 -3
  10. data/lib/mongo/collection/view/iterable.rb +2 -3
  11. data/lib/mongo/collection/view/map_reduce.rb +2 -0
  12. data/lib/mongo/collection/view/readable.rb +24 -1
  13. data/lib/mongo/collection/view/writable.rb +23 -0
  14. data/lib/mongo/collection.rb +21 -1
  15. data/lib/mongo/database/view.rb +4 -2
  16. data/lib/mongo/database.rb +6 -6
  17. data/lib/mongo/error/snapshot_session_invalid_server_version.rb +31 -0
  18. data/lib/mongo/error/snapshot_session_transaction_prohibited.rb +30 -0
  19. data/lib/mongo/error.rb +2 -0
  20. data/lib/mongo/operation/delete/op_msg.rb +2 -1
  21. data/lib/mongo/operation/find/builder/command.rb +1 -0
  22. data/lib/mongo/operation/result.rb +6 -0
  23. data/lib/mongo/operation/shared/executable.rb +4 -0
  24. data/lib/mongo/operation/shared/sessions_supported.rb +18 -2
  25. data/lib/mongo/operation/update/op_msg.rb +2 -1
  26. data/lib/mongo/server/description/features.rb +3 -1
  27. data/lib/mongo/server_selector/base.rb +26 -4
  28. data/lib/mongo/session.rb +19 -0
  29. data/lib/mongo/socket/ocsp_cache.rb +2 -3
  30. data/lib/mongo/socket.rb +1 -5
  31. data/lib/mongo/utils.rb +0 -6
  32. data/lib/mongo/version.rb +1 -1
  33. data/mongo.gemspec +1 -1
  34. data/spec/integration/read_preference_spec.rb +16 -12
  35. data/spec/mongo/collection/view/aggregation_spec.rb +71 -95
  36. data/spec/mongo/collection/view/change_stream_spec.rb +1 -1
  37. data/spec/mongo/collection/view/map_reduce_spec.rb +14 -1
  38. data/spec/mongo/operation/read_preference_op_msg_spec.rb +24 -1
  39. data/spec/mongo/server_selector_spec.rb +136 -15
  40. data/spec/mongo/socket/ssl_spec.rb +26 -58
  41. data/spec/mongo/utils_spec.rb +0 -14
  42. data/spec/runners/crud/verifier.rb +1 -2
  43. data/spec/runners/unified/assertions.rb +3 -1
  44. data/spec/runners/unified/crud_operations.rb +77 -23
  45. data/spec/runners/unified/ddl_operations.rb +29 -1
  46. data/spec/runners/unified/entity_map.rb +3 -3
  47. data/spec/runners/unified/support_operations.rb +6 -1
  48. data/spec/runners/unified/test.rb +15 -3
  49. data/spec/spec_tests/data/crud_unified/aggregate-let.yml +138 -0
  50. data/spec/spec_tests/data/crud_unified/aggregate-write-readPreference.yml +155 -0
  51. data/spec/spec_tests/data/crud_unified/db-aggregate-write-readPreference.yml +151 -0
  52. data/spec/spec_tests/data/crud_unified/deleteMany-let.yml +91 -0
  53. data/spec/spec_tests/data/crud_unified/deleteOne-let.yml +89 -0
  54. data/spec/spec_tests/data/crud_unified/find-let.yml +71 -0
  55. data/spec/spec_tests/data/crud_unified/findOneAndDelete-let.yml +88 -0
  56. data/spec/spec_tests/data/crud_unified/findOneAndReplace-let.yml +94 -0
  57. data/spec/spec_tests/data/crud_unified/findOneAndUpdate-let.yml +96 -0
  58. data/spec/spec_tests/data/crud_unified/updateMany-let.yml +103 -0
  59. data/spec/spec_tests/data/crud_unified/updateOne-let.yml +98 -0
  60. data/spec/spec_tests/data/max_staleness/ReplicaSetNoPrimary/DefaultNoMaxStaleness.yml +2 -2
  61. data/spec/spec_tests/data/max_staleness/ReplicaSetNoPrimary/LastUpdateTime.yml +3 -3
  62. data/spec/spec_tests/data/max_staleness/ReplicaSetNoPrimary/Nearest.yml +3 -3
  63. data/spec/spec_tests/data/max_staleness/ReplicaSetNoPrimary/Nearest2.yml +3 -3
  64. data/spec/spec_tests/data/max_staleness/ReplicaSetNoPrimary/PrimaryPreferred.yml +2 -2
  65. data/spec/spec_tests/data/max_staleness/ReplicaSetNoPrimary/PrimaryPreferred_tags.yml +2 -2
  66. data/spec/spec_tests/data/max_staleness/ReplicaSetNoPrimary/Secondary.yml +4 -4
  67. data/spec/spec_tests/data/max_staleness/ReplicaSetNoPrimary/SecondaryPreferred.yml +2 -2
  68. data/spec/spec_tests/data/max_staleness/ReplicaSetNoPrimary/SecondaryPreferred_tags.yml +4 -4
  69. data/spec/spec_tests/data/max_staleness/ReplicaSetNoPrimary/ZeroMaxStaleness.yml +2 -2
  70. data/spec/spec_tests/data/max_staleness/ReplicaSetWithPrimary/DefaultNoMaxStaleness.yml +2 -2
  71. data/spec/spec_tests/data/max_staleness/ReplicaSetWithPrimary/LastUpdateTime.yml +3 -3
  72. data/spec/spec_tests/data/max_staleness/ReplicaSetWithPrimary/LongHeartbeat.yml +2 -2
  73. data/spec/spec_tests/data/max_staleness/ReplicaSetWithPrimary/LongHeartbeat2.yml +2 -2
  74. data/spec/spec_tests/data/max_staleness/ReplicaSetWithPrimary/MaxStalenessTooSmall.yml +2 -2
  75. data/spec/spec_tests/data/max_staleness/ReplicaSetWithPrimary/MaxStalenessWithModePrimary.yml +2 -2
  76. data/spec/spec_tests/data/max_staleness/ReplicaSetWithPrimary/Nearest.yml +3 -3
  77. data/spec/spec_tests/data/max_staleness/ReplicaSetWithPrimary/Nearest2.yml +3 -3
  78. data/spec/spec_tests/data/max_staleness/ReplicaSetWithPrimary/Nearest_tags.yml +2 -2
  79. data/spec/spec_tests/data/max_staleness/ReplicaSetWithPrimary/PrimaryPreferred.yml +2 -2
  80. data/spec/spec_tests/data/max_staleness/ReplicaSetWithPrimary/SecondaryPreferred.yml +2 -2
  81. data/spec/spec_tests/data/max_staleness/ReplicaSetWithPrimary/SecondaryPreferred_tags.yml +5 -5
  82. data/spec/spec_tests/data/max_staleness/ReplicaSetWithPrimary/SecondaryPreferred_tags2.yml +3 -3
  83. data/spec/spec_tests/data/max_staleness/ReplicaSetWithPrimary/Secondary_tags.yml +5 -5
  84. data/spec/spec_tests/data/max_staleness/ReplicaSetWithPrimary/Secondary_tags2.yml +3 -3
  85. data/spec/spec_tests/data/max_staleness/ReplicaSetWithPrimary/ZeroMaxStaleness.yml +2 -2
  86. data/spec/spec_tests/data/max_staleness/Sharded/SmallMaxStaleness.yml +2 -2
  87. data/spec/spec_tests/data/max_staleness/Single/SmallMaxStaleness.yml +1 -1
  88. data/spec/spec_tests/data/max_staleness/Unknown/SmallMaxStaleness.yml +1 -1
  89. data/spec/spec_tests/data/sessions_unified/snapshot-sessions-not-supported-client-error.yml +69 -0
  90. data/spec/spec_tests/data/sessions_unified/snapshot-sessions-not-supported-server-error.yml +102 -0
  91. data/spec/spec_tests/data/sessions_unified/snapshot-sessions-unsupported-ops.yml +258 -0
  92. data/spec/spec_tests/data/sessions_unified/snapshot-sessions.yml +482 -0
  93. data/spec/spec_tests/sessions_unified_spec.rb +13 -0
  94. data.tar.gz.sig +0 -0
  95. metadata +36 -2
  96. metadata.gz.sig +0 -0
@@ -202,12 +202,7 @@ describe Mongo::ServerSelector do
202
202
  describe "#select_server" do
203
203
  require_no_linting
204
204
 
205
- context 'when #select_in_replica_set returns a list of nils' do
206
-
207
- let(:servers) do
208
- [ make_server(:primary) ]
209
- end
210
-
205
+ context 'replica set topology' do
211
206
  let(:cluster) do
212
207
  double('cluster').tap do |c|
213
208
  allow(c).to receive(:connected?).and_return(true)
@@ -223,19 +218,141 @@ describe Mongo::ServerSelector do
223
218
  allow(c).to receive(:scan!).and_return(true)
224
219
  allow(c).to receive(:options).and_return(server_selection_timeout: 0.1)
225
220
  allow(c).to receive(:server_selection_semaphore).and_return(nil)
221
+ allow(topology).to receive(:compatible?).and_return(true)
226
222
  end
227
223
  end
228
224
 
229
- let(:read_pref) do
230
- described_class.get(mode: :primary).tap do |pref|
231
- allow(pref).to receive(:select_in_replica_set).and_return([ nil, nil ])
225
+ let(:primary) do
226
+ make_server(:primary).tap do |server|
227
+ allow(server).to receive(:features).and_return(double("primary features"))
232
228
  end
233
229
  end
234
230
 
235
- it 'raises a NoServerAvailable error' do
236
- expect do
237
- read_pref.select_server(cluster)
238
- end.to raise_exception(Mongo::Error::NoServerAvailable)
231
+ let(:secondary) do
232
+ make_server(:secondary).tap do |server|
233
+ allow(server).to receive(:features).and_return(double("secondary features"))
234
+ end
235
+ end
236
+
237
+ context "when #select_in_replica_set returns a list of nils" do
238
+ let(:servers) do
239
+ [ primary ]
240
+ end
241
+
242
+ let(:read_pref) do
243
+ described_class.get(mode: :primary).tap do |pref|
244
+ allow(pref).to receive(:select_in_replica_set).and_return([ nil, nil ])
245
+ end
246
+ end
247
+
248
+ it 'raises a NoServerAvailable error' do
249
+ expect do
250
+ read_pref.select_server(cluster)
251
+ end.to raise_exception(Mongo::Error::NoServerAvailable)
252
+ end
253
+ end
254
+
255
+ context "write_aggregation is true" do
256
+
257
+ before do
258
+ # It does not matter for this context whether primary supports secondary wites or not,
259
+ # but we need to mock out this call.
260
+ allow(primary.features).to receive(:merge_out_on_secondary_enabled?).and_return(false)
261
+ end
262
+
263
+ context "read preference is primary" do
264
+ let(:selector) { Mongo::ServerSelector::Primary.new }
265
+
266
+ let(:servers) do
267
+ [ primary, secondary ]
268
+ end
269
+
270
+ [true, false].each do |secondary_support_writes|
271
+ context "secondary #{secondary_support_writes ? 'supports' : 'does not support' } writes" do
272
+ it "selects a primary" do
273
+ allow(secondary.features).to receive(:merge_out_on_secondary_enabled?).and_return(secondary_support_writes)
274
+
275
+ expect(selector.select_server(cluster, write_aggregation: true)).to eq(primary)
276
+ end
277
+ end
278
+ end
279
+ end
280
+
281
+ context "read preference is primary preferred" do
282
+ let(:selector) { Mongo::ServerSelector::PrimaryPreferred.new }
283
+
284
+ let(:servers) do
285
+ [ primary, secondary ]
286
+ end
287
+
288
+ [true, false].each do |secondary_support_writes|
289
+ context "secondary #{secondary_support_writes ? 'supports' : 'does not support' } writes" do
290
+ it "selects a primary" do
291
+ allow(secondary.features).to receive(:merge_out_on_secondary_enabled?).and_return(secondary_support_writes)
292
+
293
+ expect(selector.select_server(cluster, write_aggregation: true)).to eq(primary)
294
+ end
295
+ end
296
+ end
297
+ end
298
+
299
+ context "read preference is secondary preferred" do
300
+ let(:selector) { Mongo::ServerSelector::SecondaryPreferred.new }
301
+
302
+ let(:servers) do
303
+ [ primary, secondary ]
304
+ end
305
+
306
+ context "secondary supports writes" do
307
+ it "selects a secondary" do
308
+ allow(secondary.features).to receive(:merge_out_on_secondary_enabled?).and_return(true)
309
+
310
+ expect(selector.select_server(cluster, write_aggregation: true)).to eq(secondary)
311
+ end
312
+ end
313
+
314
+ context "secondary does not support writes" do
315
+ it "selects a primary" do
316
+ allow(secondary.features).to receive(:merge_out_on_secondary_enabled?).and_return(false)
317
+
318
+ expect(selector.select_server(cluster, write_aggregation: true)).to eq(primary)
319
+ end
320
+ end
321
+ end
322
+
323
+ context "read preference is secondary" do
324
+ let(:selector) { Mongo::ServerSelector::Secondary.new }
325
+
326
+ let(:servers) do
327
+ [ primary, secondary ]
328
+ end
329
+
330
+ context "secondary supports writes" do
331
+ it "selects a secondary" do
332
+ allow(secondary.features).to receive(:merge_out_on_secondary_enabled?).and_return(true)
333
+
334
+ expect(selector.select_server(cluster, write_aggregation: true)).to eq(secondary)
335
+ end
336
+ end
337
+
338
+ context "secondary does not support writes" do
339
+ it "selects a primary" do
340
+ allow(secondary.features).to receive(:merge_out_on_secondary_enabled?).and_return(false)
341
+
342
+ expect(selector.select_server(cluster, write_aggregation: true)).to eq(primary)
343
+ end
344
+ end
345
+
346
+ context "no secondaries in cluster" do
347
+ let(:servers) do
348
+ [ primary ]
349
+ end
350
+
351
+ it "selects a primary" do
352
+ expect(selector.select_server(cluster, write_aggregation: true)).to eq(primary)
353
+ end
354
+ end
355
+ end
239
356
  end
240
357
  end
241
358
 
@@ -381,8 +498,12 @@ describe Mongo::ServerSelector do
381
498
  let(:servers) { [unknown, mongos] }
382
499
  let(:selector) { described_class.primary }
383
500
 
384
- it 'returns the mongos' do
385
- expect(selector.select_server(cluster)).to eq(mongos)
501
+ [true, false].each do |write_aggregation|
502
+ context "write_aggregation is #{write_aggregation}" do
503
+ it 'returns the mongos' do
504
+ expect(selector.select_server(cluster, write_aggregation: write_aggregation)).to eq(mongos)
505
+ end
506
+ end
386
507
  end
387
508
  end
388
509
  end
@@ -306,67 +306,40 @@ describe Mongo::Socket::SSL, retry: 3 do
306
306
  end
307
307
  end
308
308
 
309
- context 'when ruby version is < 2.4.1' do
310
- ruby_version_lt '2.4.1'
311
-
312
- context 'when a key is passed, but it is not of the right type' do
313
-
314
- let(:ssl_options) do
315
- key = "This is a string not a key"
316
- {
317
- :ssl => true,
318
- :ssl_key_object => key,
319
- :ssl_cert => SpecConfig.instance.client_cert_path,
320
- :ssl_verify => false
321
- }
322
- end
323
-
324
- it 'raises a TypeError' do
325
- expect do
326
- socket
327
- end.to raise_exception(TypeError)
328
- end
329
- end
330
- end
331
-
332
309
  # Note that as of MRI 2.4, Creating a socket with the wrong key type raises
333
310
  # a NoMethodError because #private? is attempted to be called on the key.
334
311
  # In jruby 9.2 a TypeError is raised.
335
312
  # In jruby 9.1 a OpenSSL::PKey::PKeyError is raised.
336
- context 'when ruby version is >= 2.4.1' do
337
- ruby_version_gte '2.4.1'
313
+ context 'when a key is passed, but it is not of the right type' do
338
314
 
339
- context 'when a key is passed, but it is not of the right type' do
315
+ let(:ssl_options) do
316
+ key = "This is a string not a key"
317
+ {
318
+ :ssl => true,
319
+ :ssl_key_object => key,
320
+ :ssl_cert => SpecConfig.instance.client_cert_path,
321
+ :ssl_verify => false
322
+ }
323
+ end
340
324
 
341
- let(:ssl_options) do
342
- key = "This is a string not a key"
343
- {
344
- :ssl => true,
345
- :ssl_key_object => key,
346
- :ssl_cert => SpecConfig.instance.client_cert_path,
347
- :ssl_verify => false
348
- }
349
- end
350
-
351
- let(:expected_exception) do
352
- if SpecConfig.instance.jruby?
353
- if RUBY_VERSION >= '2.5.0'
354
- # jruby 9.2
355
- TypeError
356
- else
357
- # jruby 9.1
358
- OpenSSL::OpenSSLError
359
- end
325
+ let(:expected_exception) do
326
+ if SpecConfig.instance.jruby?
327
+ if RUBY_VERSION >= '2.5.0'
328
+ # jruby 9.2
329
+ TypeError
360
330
  else
361
- NoMethodError
331
+ # jruby 9.1
332
+ OpenSSL::OpenSSLError
362
333
  end
334
+ else
335
+ NoMethodError
363
336
  end
337
+ end
364
338
 
365
- it 'raises a NoMethodError' do
366
- expect do
367
- socket
368
- end.to raise_exception(expected_exception)
369
- end
339
+ it 'raises a NoMethodError' do
340
+ expect do
341
+ socket
342
+ end.to raise_exception(expected_exception)
370
343
  end
371
344
  end
372
345
 
@@ -403,13 +376,8 @@ describe Mongo::Socket::SSL, retry: 3 do
403
376
  context 'when a bad key is provided' do
404
377
 
405
378
  let(:expected_exception) do
406
- if RUBY_VERSION >= '2.4.0'
407
- # OpenSSL::PKey::PKeyError: Could not parse PKey: no start line
408
- [OpenSSL::OpenSSLError, /Could not parse PKey/]
409
- else
410
- # ArgumentError: Could not parse PKey: no start line
411
- [ArgumentError, /Could not parse PKey/]
412
- end
379
+ # OpenSSL::PKey::PKeyError: Could not parse PKey: no start line
380
+ [OpenSSL::OpenSSLError, /Could not parse PKey/]
413
381
  end
414
382
 
415
383
  let(:ssl_options) do
@@ -39,18 +39,4 @@ describe Mongo::Utils do
39
39
  }
40
40
  end
41
41
  end
42
-
43
- describe '#slice_hash' do
44
- it do
45
- hash = {'key1' => 1, :key2 => 's', :key3 => true}
46
- expect(
47
- described_class.slice_hash(hash, 'key1', :key3)
48
- ).to eq(
49
- {
50
- 'key1' => 1,
51
- :key3 => true
52
- }
53
- )
54
- end
55
- end
56
42
  end
@@ -110,8 +110,7 @@ EOT
110
110
  expected_command = expected_event.delete('command')
111
111
  actual_command = actual_event.delete('command')
112
112
 
113
- # Hash#compact is ruby 2.4+
114
- expected_presence = expected_command.select { |k, v| !v.nil? }
113
+ expected_presence = expected_command.compact
115
114
  expected_absence = expected_command.select { |k, v| v.nil? }
116
115
 
117
116
  expected_presence.each do |k, v|
@@ -62,7 +62,7 @@ module Unified
62
62
  def assert_outcome
63
63
  return unless outcome
64
64
 
65
- client = ClientRegistry.instance.global_client('authorized')
65
+ client = ClientRegistry.instance.global_client('root_authorized')
66
66
  outcome.each do |spec|
67
67
  spec = UsingHash[spec]
68
68
  collection = client.use(spec.use!('databaseName'))[spec.use!('collectionName')]
@@ -217,6 +217,8 @@ module Unified
217
217
  BSON::ObjectId === object
218
218
  when 'date'
219
219
  Time === object
220
+ when 'double'
221
+ Float === object
220
222
  else
221
223
  raise NotImplementedError, "Unhandled type #{type}"
222
224
  end
@@ -8,7 +8,9 @@ module Unified
8
8
  def find(op)
9
9
  collection = entities.get(:collection, op.use!('object'))
10
10
  use_arguments(op) do |args|
11
- opts = {}
11
+ opts = {
12
+ let: args.use('let'),
13
+ }
12
14
  if session = args.use('session')
13
15
  opts[:session] = entities.get(:session, session)
14
16
  end
@@ -29,7 +31,11 @@ module Unified
29
31
  def count_documents(op)
30
32
  collection = entities.get(:collection, op.use!('object'))
31
33
  use_arguments(op) do |args|
32
- collection.find(args.use!('filter')).count_documents
34
+ opts = {}
35
+ if session = args.use('session')
36
+ opts[:session] = entities.get(:session, session)
37
+ end
38
+ collection.find(args.use!('filter')).count_documents(**opts)
33
39
  end
34
40
  end
35
41
 
@@ -47,7 +53,11 @@ module Unified
47
53
  def distinct(op)
48
54
  collection = entities.get(:collection, op.use!('object'))
49
55
  use_arguments(op) do |args|
50
- req = collection.find(args.use!('filter')).distinct(args.use!('fieldName'))
56
+ opts = {}
57
+ if session = args.use('session')
58
+ opts[:session] = entities.get(:session, session)
59
+ end
60
+ req = collection.find(args.use!('filter'), **opts).distinct(args.use!('fieldName'), **opts)
51
61
  result = req.to_a
52
62
  end
53
63
  end
@@ -57,10 +67,15 @@ module Unified
57
67
  use_arguments(op) do |args|
58
68
  filter = args.use!('filter')
59
69
  update = args.use!('update')
60
- opts = {}
70
+ opts = {
71
+ let: args.use('let'),
72
+ }
61
73
  if return_document = args.use('returnDocument')
62
74
  opts[:return_document] = return_document.downcase.to_sym
63
75
  end
76
+ if session = args.use('session')
77
+ opts[:session] = entities.get(:session, session)
78
+ end
64
79
  collection.find_one_and_update(filter, update, **opts)
65
80
  end
66
81
  end
@@ -70,7 +85,13 @@ module Unified
70
85
  use_arguments(op) do |args|
71
86
  filter = args.use!('filter')
72
87
  update = args.use!('replacement')
73
- collection.find_one_and_replace(filter, update)
88
+ opts = {
89
+ let: args.use('let'),
90
+ }
91
+ if session = args.use('session')
92
+ opts[:session] = entities.get(:session, session)
93
+ end
94
+ collection.find_one_and_replace(filter, update, **opts)
74
95
  end
75
96
  end
76
97
 
@@ -78,7 +99,13 @@ module Unified
78
99
  collection = entities.get(:collection, op.use!('object'))
79
100
  use_arguments(op) do |args|
80
101
  filter = args.use!('filter')
81
- collection.find_one_and_delete(filter)
102
+ opts = {
103
+ let: args.use('let'),
104
+ }
105
+ if session = args.use('session')
106
+ opts[:session] = entities.get(:session, session)
107
+ end
108
+ collection.find_one_and_delete(filter, **opts)
82
109
  end
83
110
  end
84
111
 
@@ -96,25 +123,37 @@ module Unified
96
123
  def insert_many(op)
97
124
  collection = entities.get(:collection, op.use!('object'))
98
125
  use_arguments(op) do |args|
99
- options = {}
126
+ opts = {}
100
127
  unless (ordered = args.use('ordered')).nil?
101
- options[:ordered] = ordered
128
+ opts[:ordered] = ordered
129
+ end
130
+ if session = args.use('session')
131
+ opts[:session] = entities.get(:session, session)
102
132
  end
103
- collection.insert_many(args.use!('documents'), **options)
133
+ collection.insert_many(args.use!('documents'), **opts)
104
134
  end
105
135
  end
106
136
 
107
137
  def update_one(op)
108
138
  collection = entities.get(:collection, op.use!('object'))
109
139
  use_arguments(op) do |args|
110
- collection.update_one(args.use!('filter'), args.use!('update'))
140
+ opts = {
141
+ let: args.use('let'),
142
+ }
143
+ if session = args.use('session')
144
+ opts[:session] = entities.get(:session, session)
145
+ end
146
+ collection.update_one(args.use!('filter'), args.use!('update'), **opts)
111
147
  end
112
148
  end
113
149
 
114
150
  def update_many(op)
115
151
  collection = entities.get(:collection, op.use!('object'))
116
152
  use_arguments(op) do |args|
117
- collection.update_many(args.use!('filter'), args.use!('update'))
153
+ opts = {
154
+ let: args.use('let'),
155
+ }
156
+ collection.update_many(args.use!('filter'), args.use!('update'), **opts)
118
157
  end
119
158
  end
120
159
 
@@ -132,14 +171,23 @@ module Unified
132
171
  def delete_one(op)
133
172
  collection = entities.get(:collection, op.use!('object'))
134
173
  use_arguments(op) do |args|
135
- collection.delete_one(args.use!('filter'))
174
+ opts = {
175
+ let: args.use('let'),
176
+ }
177
+ if session = args.use('session')
178
+ opts[:session] = entities.get(:session, session)
179
+ end
180
+ collection.delete_one(args.use!('filter'), **opts)
136
181
  end
137
182
  end
138
183
 
139
184
  def delete_many(op)
140
185
  collection = entities.get(:collection, op.use!('object'))
141
186
  use_arguments(op) do |args|
142
- collection.delete_many(args.use!('filter'))
187
+ opts = {
188
+ let: args.use('let'),
189
+ }
190
+ collection.delete_many(args.use!('filter'), **opts)
143
191
  end
144
192
  end
145
193
 
@@ -157,6 +205,22 @@ module Unified
157
205
  end
158
206
  end
159
207
 
208
+ def aggregate(op)
209
+ obj = entities.get_any(op.use!('object'))
210
+ args = op.use!('arguments')
211
+ pipeline = args.use!('pipeline')
212
+ opts = {
213
+ let: args.use('let'),
214
+ }
215
+ if session = args.use('session')
216
+ opts[:session] = entities.get(:session, session)
217
+ end
218
+ unless args.empty?
219
+ raise NotImplementedError, "Unhandled spec keys: #{args} in #{test_spec}"
220
+ end
221
+ obj.aggregate(pipeline, **opts).to_a
222
+ end
223
+
160
224
  private
161
225
 
162
226
  def convert_bulk_write_spec(spec)
@@ -192,15 +256,5 @@ module Unified
192
256
  end
193
257
  {Utils.underscore(op) =>out}
194
258
  end
195
-
196
- def aggregate(op)
197
- obj = entities.get_any(op.use!('object'))
198
- args = op.use!('arguments')
199
- pipeline = args.use!('pipeline')
200
- unless args.empty?
201
- raise NotImplementedError, "Unhandled spec keys: #{test_spec}"
202
- end
203
- obj.aggregate(pipeline).to_a
204
- end
205
259
  end
206
260
  end
@@ -7,7 +7,13 @@ module Unified
7
7
 
8
8
  def list_databases(op)
9
9
  client = entities.get(:client, op.use!('object'))
10
- client.list_databases
10
+ use_arguments(op) do |args|
11
+ opts = {}
12
+ if session = args.use('session')
13
+ opts[:session] = entities.get(:session, session)
14
+ end
15
+ client.list_databases({}, false, **opts)
16
+ end
11
17
  end
12
18
 
13
19
  def create_collection(op)
@@ -28,6 +34,17 @@ module Unified
28
34
  end
29
35
  end
30
36
 
37
+ def list_collections(op)
38
+ database = entities.get(:database, op.use!('object'))
39
+ use_arguments(op) do |args|
40
+ opts = {}
41
+ if session = args.use('session')
42
+ opts[:session] = entities.get(:session, session)
43
+ end
44
+ database.list_collections(**opts)
45
+ end
46
+ end
47
+
31
48
  def drop_collection(op)
32
49
  database = entities.get(:database, op.use!('object'))
33
50
  use_arguments(op) do |args|
@@ -58,6 +75,17 @@ module Unified
58
75
  assert_collection_exists(op, false)
59
76
  end
60
77
 
78
+ def list_indexes(op)
79
+ collection = entities.get(:collection, op.use!('object'))
80
+ use_arguments(op) do |args|
81
+ opts = {}
82
+ if session = args.use('session')
83
+ opts[:session] = entities.get(:session, session)
84
+ end
85
+ collection.indexes(**opts).to_a
86
+ end
87
+ end
88
+
61
89
  def create_index(op)
62
90
  collection = entities.get(:collection, op.use!('object'))
63
91
  use_arguments(op) do |args|
@@ -3,6 +3,8 @@
3
3
 
4
4
  module Unified
5
5
  class EntityMap
6
+ extend Forwardable
7
+
6
8
  def initialize
7
9
  @map = {}
8
10
  end
@@ -35,8 +37,6 @@ module Unified
35
37
  raise Error::EntityMissing, "There is no #{id} known"
36
38
  end
37
39
 
38
- def [](type)
39
- @map[type]
40
- end
40
+ def_delegators :@map, :[], :fetch
41
41
  end
42
42
  end
@@ -13,7 +13,12 @@ module Unified
13
13
 
14
14
  cmd = args.use!('command')
15
15
 
16
- database.command(cmd)
16
+ opts = {}
17
+ if session = args.use('session')
18
+ opts[:session] = entities.get(:session, session)
19
+ end
20
+
21
+ database.command(cmd, **opts)
17
22
  end
18
23
  end
19
24
 
@@ -160,13 +160,24 @@ module Unified
160
160
  end
161
161
  when 'database'
162
162
  client = entities.get(:client, spec.use!('client'))
163
- client.use(spec.use!('databaseName')).database
163
+ opts = Utils.snakeize_hash(spec.use('databaseOptions') || {})
164
+ .merge(database: spec.use!('databaseName'))
165
+ if opts.key?(:read_preference)
166
+ opts[:read] = opts.delete(:read_preference)
167
+ if opts[:read].key?(:max_staleness_seconds)
168
+ opts[:read][:max_staleness] = opts[:read].delete(:max_staleness_seconds)
169
+ end
170
+ end
171
+ client.with(opts).database
164
172
  when 'collection'
165
173
  database = entities.get(:database, spec.use!('database'))
166
174
  # TODO verify
167
175
  opts = Utils.snakeize_hash(spec.use('collectionOptions') || {})
168
176
  if opts.key?(:read_preference)
169
177
  opts[:read] = opts.delete(:read_preference)
178
+ if opts[:read].key?(:max_staleness_seconds)
179
+ opts[:read][:max_staleness] = opts[:read].delete(:max_staleness_seconds)
180
+ end
170
181
  end
171
182
  database[spec.use!('collectionName'), opts]
172
183
  when 'bucket'
@@ -195,7 +206,8 @@ module Unified
195
206
  def set_initial_data
196
207
  @spec['initialData']&.each do |entity_spec|
197
208
  spec = UsingHash[entity_spec]
198
- collection = root_authorized_client.use(spec.use!('databaseName'))[spec.use!('collectionName')]
209
+ collection = root_authorized_client.with(write_concern: {w: :majority}).
210
+ use(spec.use!('databaseName'))[spec.use!('collectionName')]
199
211
  collection.drop
200
212
  docs = spec.use!('documents')
201
213
  if docs.any?
@@ -268,7 +280,7 @@ module Unified
268
280
  end
269
281
  if expected_error = op.use('expectError')
270
282
  begin
271
- send(method_name, op)
283
+ public_send(method_name, op)
272
284
  rescue Mongo::Error, BSON::String::IllegalKey => e
273
285
  if expected_error.use('isClientError')
274
286
  # isClientError doesn't actually mean a client error.