mongo 2.16.1 → 2.17.0

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 (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.