mongo 2.16.0 → 2.17.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (99) 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/push_monitor.rb +4 -1
  28. data/lib/mongo/server_selector/base.rb +26 -4
  29. data/lib/mongo/session.rb +19 -0
  30. data/lib/mongo/socket/ocsp_cache.rb +2 -3
  31. data/lib/mongo/socket.rb +1 -5
  32. data/lib/mongo/utils.rb +0 -6
  33. data/lib/mongo/version.rb +1 -1
  34. data/mongo.gemspec +1 -1
  35. data/spec/integration/read_preference_spec.rb +16 -12
  36. data/spec/mongo/collection/view/aggregation_spec.rb +71 -95
  37. data/spec/mongo/collection/view/change_stream_spec.rb +1 -1
  38. data/spec/mongo/collection/view/map_reduce_spec.rb +14 -1
  39. data/spec/mongo/operation/read_preference_op_msg_spec.rb +24 -1
  40. data/spec/mongo/server/monitor/connection_spec.rb +22 -0
  41. data/spec/mongo/server/push_monitor_spec.rb +101 -0
  42. data/spec/mongo/server_selector_spec.rb +136 -15
  43. data/spec/mongo/socket/ssl_spec.rb +26 -58
  44. data/spec/mongo/utils_spec.rb +0 -14
  45. data/spec/runners/crud/verifier.rb +1 -2
  46. data/spec/runners/unified/assertions.rb +3 -1
  47. data/spec/runners/unified/crud_operations.rb +77 -23
  48. data/spec/runners/unified/ddl_operations.rb +29 -1
  49. data/spec/runners/unified/entity_map.rb +3 -3
  50. data/spec/runners/unified/support_operations.rb +6 -1
  51. data/spec/runners/unified/test.rb +15 -3
  52. data/spec/spec_tests/data/crud_unified/aggregate-let.yml +138 -0
  53. data/spec/spec_tests/data/crud_unified/aggregate-write-readPreference.yml +155 -0
  54. data/spec/spec_tests/data/crud_unified/db-aggregate-write-readPreference.yml +151 -0
  55. data/spec/spec_tests/data/crud_unified/deleteMany-let.yml +91 -0
  56. data/spec/spec_tests/data/crud_unified/deleteOne-let.yml +89 -0
  57. data/spec/spec_tests/data/crud_unified/find-let.yml +71 -0
  58. data/spec/spec_tests/data/crud_unified/findOneAndDelete-let.yml +88 -0
  59. data/spec/spec_tests/data/crud_unified/findOneAndReplace-let.yml +94 -0
  60. data/spec/spec_tests/data/crud_unified/findOneAndUpdate-let.yml +96 -0
  61. data/spec/spec_tests/data/crud_unified/updateMany-let.yml +103 -0
  62. data/spec/spec_tests/data/crud_unified/updateOne-let.yml +98 -0
  63. data/spec/spec_tests/data/max_staleness/ReplicaSetNoPrimary/DefaultNoMaxStaleness.yml +2 -2
  64. data/spec/spec_tests/data/max_staleness/ReplicaSetNoPrimary/LastUpdateTime.yml +3 -3
  65. data/spec/spec_tests/data/max_staleness/ReplicaSetNoPrimary/Nearest.yml +3 -3
  66. data/spec/spec_tests/data/max_staleness/ReplicaSetNoPrimary/Nearest2.yml +3 -3
  67. data/spec/spec_tests/data/max_staleness/ReplicaSetNoPrimary/PrimaryPreferred.yml +2 -2
  68. data/spec/spec_tests/data/max_staleness/ReplicaSetNoPrimary/PrimaryPreferred_tags.yml +2 -2
  69. data/spec/spec_tests/data/max_staleness/ReplicaSetNoPrimary/Secondary.yml +4 -4
  70. data/spec/spec_tests/data/max_staleness/ReplicaSetNoPrimary/SecondaryPreferred.yml +2 -2
  71. data/spec/spec_tests/data/max_staleness/ReplicaSetNoPrimary/SecondaryPreferred_tags.yml +4 -4
  72. data/spec/spec_tests/data/max_staleness/ReplicaSetNoPrimary/ZeroMaxStaleness.yml +2 -2
  73. data/spec/spec_tests/data/max_staleness/ReplicaSetWithPrimary/DefaultNoMaxStaleness.yml +2 -2
  74. data/spec/spec_tests/data/max_staleness/ReplicaSetWithPrimary/LastUpdateTime.yml +3 -3
  75. data/spec/spec_tests/data/max_staleness/ReplicaSetWithPrimary/LongHeartbeat.yml +2 -2
  76. data/spec/spec_tests/data/max_staleness/ReplicaSetWithPrimary/LongHeartbeat2.yml +2 -2
  77. data/spec/spec_tests/data/max_staleness/ReplicaSetWithPrimary/MaxStalenessTooSmall.yml +2 -2
  78. data/spec/spec_tests/data/max_staleness/ReplicaSetWithPrimary/MaxStalenessWithModePrimary.yml +2 -2
  79. data/spec/spec_tests/data/max_staleness/ReplicaSetWithPrimary/Nearest.yml +3 -3
  80. data/spec/spec_tests/data/max_staleness/ReplicaSetWithPrimary/Nearest2.yml +3 -3
  81. data/spec/spec_tests/data/max_staleness/ReplicaSetWithPrimary/Nearest_tags.yml +2 -2
  82. data/spec/spec_tests/data/max_staleness/ReplicaSetWithPrimary/PrimaryPreferred.yml +2 -2
  83. data/spec/spec_tests/data/max_staleness/ReplicaSetWithPrimary/SecondaryPreferred.yml +2 -2
  84. data/spec/spec_tests/data/max_staleness/ReplicaSetWithPrimary/SecondaryPreferred_tags.yml +5 -5
  85. data/spec/spec_tests/data/max_staleness/ReplicaSetWithPrimary/SecondaryPreferred_tags2.yml +3 -3
  86. data/spec/spec_tests/data/max_staleness/ReplicaSetWithPrimary/Secondary_tags.yml +5 -5
  87. data/spec/spec_tests/data/max_staleness/ReplicaSetWithPrimary/Secondary_tags2.yml +3 -3
  88. data/spec/spec_tests/data/max_staleness/ReplicaSetWithPrimary/ZeroMaxStaleness.yml +2 -2
  89. data/spec/spec_tests/data/max_staleness/Sharded/SmallMaxStaleness.yml +2 -2
  90. data/spec/spec_tests/data/max_staleness/Single/SmallMaxStaleness.yml +1 -1
  91. data/spec/spec_tests/data/max_staleness/Unknown/SmallMaxStaleness.yml +1 -1
  92. data/spec/spec_tests/data/sessions_unified/snapshot-sessions-not-supported-client-error.yml +69 -0
  93. data/spec/spec_tests/data/sessions_unified/snapshot-sessions-not-supported-server-error.yml +102 -0
  94. data/spec/spec_tests/data/sessions_unified/snapshot-sessions-unsupported-ops.yml +258 -0
  95. data/spec/spec_tests/data/sessions_unified/snapshot-sessions.yml +482 -0
  96. data/spec/spec_tests/sessions_unified_spec.rb +13 -0
  97. data.tar.gz.sig +0 -0
  98. metadata +1076 -1040
  99. metadata.gz.sig +2 -2
data/lib/mongo/error.rb CHANGED
@@ -223,6 +223,8 @@ require 'mongo/error/no_server_available'
223
223
  require 'mongo/error/no_srv_records'
224
224
  require 'mongo/error/session_ended'
225
225
  require 'mongo/error/sessions_not_supported'
226
+ require 'mongo/error/snapshot_session_invalid_server_version'
227
+ require 'mongo/error/snapshot_session_transaction_prohibited'
226
228
  require 'mongo/error/operation_failure'
227
229
  require 'mongo/error/pool_closed_error'
228
230
  require 'mongo/error/raise_original_error'
@@ -37,7 +37,8 @@ module Mongo
37
37
  { delete: coll_name,
38
38
  Protocol::Msg::DATABASE_IDENTIFIER => db_name,
39
39
  ordered: ordered?,
40
- }.tap do |selector|
40
+ let: spec[:let],
41
+ }.compact.tap do |selector|
41
42
  if hint = spec[:hint]
42
43
  validate_hint_on_update(connection, selector)
43
44
  selector[:hint] = hint
@@ -35,6 +35,7 @@ module Mongo
35
35
  comment: 'comment',
36
36
  filter: 'filter',
37
37
  hint: 'hint',
38
+ let: 'let',
38
39
  limit: 'limit',
39
40
  max_scan: 'maxScan',
40
41
  max_time_ms: 'maxTimeMS',
@@ -429,6 +429,12 @@ module Mongo
429
429
  !!(first_document && first_document['writeConcernError'])
430
430
  end
431
431
 
432
+ def snapshot_timestamp
433
+ if doc = reply.documents.first
434
+ doc['cursor']&.[]('atClusterTime') || doc['atClusterTime']
435
+ end
436
+ end
437
+
432
438
  private
433
439
 
434
440
  def aggregate_returned_count
@@ -43,6 +43,10 @@ module Mongo
43
43
  session.pin_to_service(connection.service_id)
44
44
  end
45
45
  end
46
+
47
+ if session.snapshot? && !session.snapshot_timestamp
48
+ session.snapshot_timestamp = result.snapshot_timestamp
49
+ end
46
50
  end
47
51
  process_result(result, connection)
48
52
  end
@@ -199,8 +199,13 @@ module Mongo
199
199
  read_doc
200
200
  else
201
201
  # In replica sets, read preference is passed to the server if one
202
- # is specified by the application, and there is no default.
203
- read&.to_doc
202
+ # is specified by the application, except for primary read preferences.
203
+ read_doc = BSON::Document.new(read&.to_doc || {})
204
+ if [nil, 'primary'].include?(read_doc['mode'])
205
+ nil
206
+ else
207
+ read_doc
208
+ end
204
209
  end
205
210
 
206
211
  if read_doc
@@ -224,6 +229,17 @@ module Mongo
224
229
  then
225
230
  sel[:recoveryToken] = session.recovery_token
226
231
  end
232
+
233
+ if session.snapshot?
234
+ unless connection.description.server_version_gte?('5.0')
235
+ raise Error::SnapshotSessionInvalidServerVersion
236
+ end
237
+
238
+ sel[:readConcern] = {level: 'snapshot'}
239
+ if session.snapshot_timestamp
240
+ sel[:readConcern][:atClusterTime] = session.snapshot_timestamp
241
+ end
242
+ end
227
243
  end
228
244
 
229
245
  def build_message(connection, context)
@@ -37,7 +37,8 @@ module Mongo
37
37
  {
38
38
  update: coll_name,
39
39
  ordered: ordered?,
40
- }
40
+ let: spec[:let]
41
+ }.compact
41
42
  end
42
43
 
43
44
  def message(connection)
@@ -35,9 +35,11 @@ module Mongo
35
35
  # - 8 => 4.2
36
36
  # - 9 => 4.4
37
37
  # - 13 => 5.0
38
+ # - 14 => 5.1
38
39
  #
39
40
  # @since 2.0.0
40
41
  MAPPINGS = {
42
+ merge_out_on_secondary: 13,
41
43
  retryable_write_error_label: 9,
42
44
  commit_quorum: 9,
43
45
  # Server versions older than 4.2 do not reliably validate options
@@ -78,7 +80,7 @@ module Mongo
78
80
  # The wire protocol versions that this version of the driver supports.
79
81
  #
80
82
  # @since 2.0.0
81
- DRIVER_WIRE_VERSIONS = (2..13).freeze
83
+ DRIVER_WIRE_VERSIONS = (6..14).freeze
82
84
 
83
85
  # Create the methods for each mapping to tell if they are supported.
84
86
  #
@@ -110,7 +110,7 @@ module Mongo
110
110
  if new_description.topology_version
111
111
  @topology_version = new_description.topology_version
112
112
  end
113
- rescue Mongo::Error => exc
113
+ rescue IOError, SocketError, SystemCallError, Mongo::Error => exc
114
114
  stop_requested = @lock.synchronize { @stop_requested }
115
115
  if stop_requested
116
116
  # Ignore the exception, see RUBY-2771.
@@ -123,6 +123,9 @@ module Mongo
123
123
  log_prefix: options[:log_prefix],
124
124
  bg_error_backtrace: options[:bg_error_backtrace],
125
125
  )
126
+
127
+ # Avoid tight looping in push monitor - see RUBY-2806.
128
+ sleep(0.5)
126
129
  end
127
130
 
128
131
  def check
@@ -162,6 +162,8 @@ module Mongo
162
162
  # Deprecated and ignored.
163
163
  # @param [ Session | nil ] session Optional session to take into account
164
164
  # for mongos pinning. Added in version 2.10.0.
165
+ # @param [ true | false ] write_aggregation Whether we need a server that
166
+ # supports writing aggregations (e.g. with $merge/$out) on secondaries.
165
167
  #
166
168
  # @return [ Mongo::Server ] A server matching the server preference.
167
169
  #
@@ -172,7 +174,7 @@ module Mongo
172
174
  # lint mode is enabled.
173
175
  #
174
176
  # @since 2.0.0
175
- def select_server(cluster, ping = nil, session = nil)
177
+ def select_server(cluster, ping = nil, session = nil, write_aggregation: false)
176
178
  if cluster.topology.is_a?(Cluster::Topology::LoadBalanced)
177
179
  return cluster.servers.first
178
180
  end
@@ -243,7 +245,7 @@ module Mongo
243
245
  =end
244
246
 
245
247
  loop do
246
- server = try_select_server(cluster)
248
+ server = try_select_server(cluster, write_aggregation: write_aggregation)
247
249
 
248
250
  if server
249
251
  unless cluster.topology.compatible?
@@ -294,11 +296,31 @@ module Mongo
294
296
  # Tries to find a suitable server, returns the server if one is available
295
297
  # or nil if there isn't a suitable server.
296
298
  #
299
+ # @param [ Mongo::Cluster ] cluster The cluster from which to select
300
+ # an eligible server.
301
+ # @param [ true | false ] write_aggregation Whether we need a server that
302
+ # supports writing aggregations (e.g. with $merge/$out) on secondaries.
303
+ #
297
304
  # @return [ Server | nil ] A suitable server, if one exists.
298
305
  #
299
306
  # @api private
300
- def try_select_server(cluster)
301
- servers = suitable_servers(cluster)
307
+ def try_select_server(cluster, write_aggregation: false)
308
+ servers = if write_aggregation && cluster.replica_set?
309
+ # 1. Check if ALL servers in cluster support secondary writes.
310
+ is_write_supported = cluster.servers.reduce(true) do |res, server|
311
+ res && server.features.merge_out_on_secondary_enabled?
312
+ end
313
+
314
+ if is_write_supported
315
+ # 2. If all servers support secondary writes, we respect read preference.
316
+ suitable_servers(cluster)
317
+ else
318
+ # 3. Otherwise we fallback to primary for replica set.
319
+ [cluster.servers.detect(&:primary?)]
320
+ end
321
+ else
322
+ suitable_servers(cluster)
323
+ end
302
324
 
303
325
  # This list of servers may be ordered in a specific way
304
326
  # by the selector (e.g. for secondary preferred, the first
data/lib/mongo/session.rb CHANGED
@@ -56,10 +56,16 @@ module Mongo
56
56
  # - *:mode* -- the read preference as a string or symbol; valid values are
57
57
  # *:primary*, *:primary_preferred*, *:secondary*, *:secondary_preferred*
58
58
  # and *:nearest*.
59
+ # @option options [ true | false ] :snapshot Set up the session for
60
+ # snapshot reads.
59
61
  #
60
62
  # @since 2.5.0
61
63
  # @api private
62
64
  def initialize(server_session, client, options = {})
65
+ if options[:causal_consistency] && options[:snapshot]
66
+ raise ArgumentError, ':causal_consistency and :snapshot options cannot be both set on a session'
67
+ end
68
+
63
69
  @server_session = server_session
64
70
  options = options.dup
65
71
 
@@ -83,6 +89,12 @@ module Mongo
83
89
  @client.cluster
84
90
  end
85
91
 
92
+ # @return [ true | false ] Whether the session is configured for snapshot
93
+ # reads.
94
+ def snapshot?
95
+ !!options[:snapshot]
96
+ end
97
+
86
98
  # @return [ BSON::Timestamp ] The latest seen operation time for this session.
87
99
  #
88
100
  # @since 2.5.0
@@ -506,6 +518,10 @@ module Mongo
506
518
  =end
507
519
  end
508
520
 
521
+ if snapshot?
522
+ raise Mongo::Error::SnapshotSessionTransactionProhibited
523
+ end
524
+
509
525
  check_if_ended!
510
526
 
511
527
  if within_states?(STARTING_TRANSACTION_STATE, TRANSACTION_IN_PROGRESS_STATE)
@@ -1024,6 +1040,9 @@ module Mongo
1024
1040
  @server_session.txn_num
1025
1041
  end
1026
1042
 
1043
+ # @api private
1044
+ attr_accessor :snapshot_timestamp
1045
+
1027
1046
  private
1028
1047
 
1029
1048
  # Get the read concern the session will use when starting a transaction.
@@ -21,8 +21,7 @@ module Mongo
21
21
  # This module caches OCSP responses for their indicated validity time.
22
22
  #
23
23
  # The key is the CertificateId used for the OCSP request.
24
- # The value is the SingleResponse on Ruby 2.4+, or the OpenStruct
25
- # emulation of it on Ruby 2.3.
24
+ # The value is the SingleResponse.
26
25
  #
27
26
  # @api private
28
27
  module OcspCache
@@ -40,7 +39,7 @@ module Mongo
40
39
  # expire by the time caller uses them. The caller should not perform
41
40
  # update time checks on the returned response.
42
41
  #
43
- # @return [ OpenSSL::OCSP::SingleResponse | OpenStruct ] The previously
42
+ # @return [ OpenSSL::OCSP::SingleResponse ] The previously
44
43
  # retrieved response.
45
44
  module_function def get(cert_id)
46
45
  resp = responses.detect do |resp|
data/lib/mongo/socket.rb CHANGED
@@ -362,11 +362,7 @@ module Mongo
362
362
  end
363
363
 
364
364
  def allocate_string(capacity)
365
- if RUBY_VERSION >= '2.4.0'
366
- String.new('', :capacity => capacity, :encoding => 'BINARY')
367
- else
368
- ('x'*capacity).force_encoding('BINARY')
369
- end
365
+ String.new('', :capacity => capacity, :encoding => 'BINARY')
370
366
  end
371
367
 
372
368
  def read_buffer_size
data/lib/mongo/utils.rb CHANGED
@@ -101,11 +101,5 @@ module Mongo
101
101
  module_function def monotonic_time
102
102
  Process.clock_gettime(Process::CLOCK_MONOTONIC)
103
103
  end
104
-
105
- # Hash#compact implementation for Ruby 2.3/2.4
106
- # Implementation based on activesupport 5.2.3
107
- module_function def slice_hash(hash, *keys)
108
- keys.each_with_object({}) { |k, res| res[k] = hash[k] if hash.key?(k) }
109
- end
110
104
  end
111
105
  end
data/lib/mongo/version.rb CHANGED
@@ -20,5 +20,5 @@ module Mongo
20
20
  # The current version of the driver.
21
21
  #
22
22
  # @since 2.0.0
23
- VERSION = '2.16.0'.freeze
23
+ VERSION = '2.17.0'.freeze
24
24
  end
data/mongo.gemspec CHANGED
@@ -36,7 +36,7 @@ Gem::Specification.new do |s|
36
36
  s.require_paths = ['lib']
37
37
  s.bindir = 'bin'
38
38
 
39
- s.required_ruby_version = ">= 2.4"
39
+ s.required_ruby_version = ">= 2.5"
40
40
 
41
41
  s.add_dependency 'bson', '>=4.8.2', '<5.0.0'
42
42
  end
@@ -45,16 +45,6 @@ describe 'Read preference' do
45
45
  {}
46
46
  end
47
47
 
48
- shared_examples_for 'sends expected read preference when reading' do
49
- it 'sends expected read preference when reading' do
50
- read_operation
51
-
52
- event = subscriber.single_command_started_event('find')
53
- actual_preference = event.command['$readPreference']
54
- expect(actual_preference).to eq(expected_read_preference)
55
- end
56
- end
57
-
58
48
  shared_examples_for 'does not send read preference when reading' do
59
49
  it 'does not send read preference when reading' do
60
50
  read_operation
@@ -95,7 +85,17 @@ describe 'Read preference' do
95
85
  context 'server supporting OP_MSG' do
96
86
  min_server_fcv '3.6'
97
87
 
98
- it_behaves_like 'sends expected read preference when reading'
88
+ it 'sends expected read preference when reading' do
89
+ read_operation
90
+
91
+ event = subscriber.single_command_started_event('find')
92
+ actual_preference = event.command['$readPreference']
93
+ if expected_read_preference&.[]("mode") == "primary"
94
+ expect(actual_preference).to be_nil
95
+ else
96
+ expect(actual_preference).to eq(expected_read_preference)
97
+ end
98
+ end
99
99
  end
100
100
  end
101
101
 
@@ -307,7 +307,11 @@ describe 'Read preference' do
307
307
 
308
308
  event = subscriber.single_command_started_event('find')
309
309
  actual_preference = event.command['$readPreference']
310
- expect(actual_preference).to eq(expected_read_preference)
310
+ if expected_read_preference&.[]("mode") == "primary"
311
+ expect(actual_preference).to be_nil
312
+ else
313
+ expect(actual_preference).to eq(expected_read_preference)
314
+ end
311
315
  end
312
316
  end
313
317
  end
@@ -29,8 +29,16 @@ describe Mongo::Collection::View::Aggregation do
29
29
  described_class.new(view, pipeline, options)
30
30
  end
31
31
 
32
+ let(:server) do
33
+ double('server')
34
+ end
35
+
36
+ let(:session) do
37
+ double('session')
38
+ end
39
+
32
40
  let(:aggregation_spec) do
33
- aggregation.send(:aggregate_spec, double('session'))
41
+ aggregation.send(:aggregate_spec, server, session, nil)
34
42
  end
35
43
 
36
44
  before do
@@ -351,15 +359,15 @@ describe Mongo::Collection::View::Aggregation do
351
359
 
352
360
  describe '#aggregate_spec' do
353
361
 
354
- context 'when the collection has a read preference' do
362
+ context 'when a read preference is given' do
355
363
 
356
364
  let(:read_preference) do
357
- {mode: :secondary}
365
+ BSON::Document.new({mode: :secondary})
358
366
  end
359
367
 
360
368
  it 'includes the read preference in the spec' do
361
- allow(authorized_collection).to receive(:read_preference).and_return(read_preference)
362
- expect(aggregation_spec[:read]).to eq(read_preference)
369
+ spec = aggregation.send(:aggregate_spec, server, session, read_preference)
370
+ expect(spec[:read]).to eq(read_preference)
363
371
  end
364
372
  end
365
373
 
@@ -570,109 +578,77 @@ describe Mongo::Collection::View::Aggregation do
570
578
  end
571
579
 
572
580
  context 'when $out is in the pipeline' do
573
-
574
- let(:pipeline) do
575
- [{
576
- "$group" => {
577
- "_id" => "$city",
578
- "totalpop" => { "$sum" => "$pop" }
581
+ [['$out', 'string'], [:$out, 'symbol']].each do |op, type|
582
+ context "when #{op} is a #{type}" do
583
+ let(:pipeline) do
584
+ [{
585
+ "$group" => {
586
+ "_id" => "$city",
587
+ "totalpop" => { "$sum" => "$pop" }
588
+ }
589
+ },
590
+ {
591
+ op => 'output_collection'
579
592
  }
580
- },
581
- {
582
- '$out' => 'output_collection'
583
- }
584
- ]
585
- end
586
-
587
- before do
588
- authorized_client['output_collection'].delete_many
589
- end
590
-
591
- context 'when $out is a string' do
592
-
593
- it 'does not allow the operation on a secondary' do
594
- expect(aggregation.send(:secondary_ok?)).to be(false)
595
- end
596
- end
597
-
598
- context 'when $out is a symbol' do
599
-
600
- let(:pipeline) do
601
- [{
602
- "$group" => {
603
- "_id" => "$city",
604
- "totalpop" => { "$sum" => "$pop" }
605
- }
606
- },
607
- {
608
- :$out => 'output_collection'
609
- }
610
- ]
611
- end
612
-
613
- it 'does not allow the operation on a secondary' do
614
- expect(aggregation.send(:secondary_ok?)).to be(false)
615
- end
616
- end
593
+ ]
594
+ end
617
595
 
596
+ before do
597
+ authorized_client['output_collection'].delete_many
598
+ end
618
599
 
619
- context 'when the server is not a valid for writing' do
600
+ let(:features) do
601
+ double()
602
+ end
620
603
 
621
- it 'reroutes the operation to a primary' do
622
- allow(aggregation).to receive(:valid_server?).and_return(false)
623
- expect(Mongo::Logger.logger).to receive(:warn).and_call_original
624
- aggregation.to_a
625
- end
626
- end
604
+ let(:server) do
605
+ double().tap do |server|
606
+ allow(server).to receive(:features).and_return(features)
607
+ end
608
+ end
627
609
 
628
- context 'when the server is a valid for writing' do
610
+ context 'when the view has a write concern' do
629
611
 
630
- it 'does not reroute the operation to a primary' do
631
- expect(Mongo::Logger.logger).not_to receive(:warn)
632
- aggregation.to_a
633
- end
612
+ let(:collection) do
613
+ authorized_collection.with(write: INVALID_WRITE_CONCERN)
614
+ end
634
615
 
635
- context 'when the view has a write concern' do
616
+ let(:view) do
617
+ Mongo::Collection::View.new(collection, selector, view_options)
618
+ end
636
619
 
637
- let(:collection) do
638
- authorized_collection.with(write: INVALID_WRITE_CONCERN)
639
- end
620
+ context 'when the server supports write concern on the aggregate command' do
621
+ min_server_fcv '3.4'
640
622
 
641
- let(:view) do
642
- Mongo::Collection::View.new(collection, selector, view_options)
643
- end
623
+ it 'uses the write concern' do
624
+ expect {
625
+ aggregation.to_a
626
+ }.to raise_exception(Mongo::Error::OperationFailure)
627
+ end
628
+ end
644
629
 
645
- context 'when the server supports write concern on the aggregate command' do
646
- min_server_fcv '3.4'
630
+ context 'when the server does not support write concern on the aggregation command' do
631
+ max_server_version '3.2'
647
632
 
648
- it 'uses the write concern' do
649
- expect {
650
- aggregation.to_a
651
- }.to raise_exception(Mongo::Error::OperationFailure)
652
- end
653
- end
633
+ let(:documents) do
634
+ [
635
+ { city: "Berlin", pop: 18913, neighborhood: "Kreuzberg" },
636
+ { city: "Berlin", pop: 84143, neighborhood: "Mitte" },
637
+ { city: "New York", pop: 40270, neighborhood: "Brooklyn" }
638
+ ]
639
+ end
654
640
 
655
- context 'when the server does not support write concern on the aggregation command' do
656
- max_server_version '3.2'
641
+ before do
642
+ authorized_collection.insert_many(documents)
643
+ aggregation.to_a
644
+ end
657
645
 
658
- let(:documents) do
659
- [
660
- { city: "Berlin", pop: 18913, neighborhood: "Kreuzberg" },
661
- { city: "Berlin", pop: 84143, neighborhood: "Mitte" },
662
- { city: "New York", pop: 40270, neighborhood: "Brooklyn" }
663
- ]
664
- end
665
-
666
- before do
667
- authorized_collection.insert_many(documents)
668
- aggregation.to_a
669
- end
670
-
671
- it 'does not apply the write concern' do
672
- expect(authorized_client['output_collection'].find.count).to eq(2)
673
- end
674
- end
675
- end
646
+ it 'does not apply the write concern' do
647
+ expect(authorized_client['output_collection'].find.count).to eq(2)
648
+ end
649
+ end
650
+ end
651
+ end
676
652
  end
677
653
  end
678
654
  end
@@ -56,7 +56,7 @@ describe Mongo::Collection::View::ChangeStream do
56
56
 
57
57
  let(:command_spec) do
58
58
  change_stream.send(:instance_variable_set, '@resuming', false)
59
- change_stream.send(:aggregate_spec, double('session'))
59
+ change_stream.send(:aggregate_spec, double('server'), double('session'), nil)
60
60
  end
61
61
 
62
62
  let(:cursor) do
@@ -60,6 +60,14 @@ describe Mongo::Collection::View::MapReduce do
60
60
  described_class.new(view, map, reduce, options)
61
61
  end
62
62
 
63
+ describe '#initialize' do
64
+ it 'warns of deprecation' do
65
+ Mongo::Logger.logger.should receive(:warn).with('MONGODB | The map_reduce operation is deprecated, please use the aggregation pipeline instead')
66
+
67
+ map_reduce
68
+ end
69
+ end
70
+
63
71
  describe '#map_function' do
64
72
 
65
73
  it 'returns the map function' do
@@ -672,7 +680,12 @@ describe Mongo::Collection::View::MapReduce do
672
680
  end
673
681
 
674
682
  it 'does not reroute the operation to a primary' do
675
- expect(Mongo::Logger.logger).not_to receive(:warn)
683
+ # We produce a deprecation warning, but there shouldn't be
684
+ # the reroute warning.
685
+ expect(Mongo::Logger.logger).to receive(:warn).once do |msg|
686
+ expect(msg).not_to include('Rerouting the MapReduce operation to the primary server')
687
+ end
688
+
676
689
  map_reduce.to_a
677
690
  end
678
691
  end
@@ -99,6 +99,29 @@ describe Mongo::Operation::SessionsSupported do
99
99
  end
100
100
  end
101
101
 
102
+ shared_examples_for 'sends read preference correctly for replica set' do
103
+ context "when read preference mode is primary" do
104
+ let(:mode) { :primary}
105
+
106
+ it_behaves_like 'does not modify selector'
107
+ end
108
+ %i(primary_preferred secondary secondary_preferred nearest).each do |_mode|
109
+ active_mode = _mode
110
+
111
+ context "when read preference mode is #{active_mode}" do
112
+ let(:mode) { active_mode }
113
+
114
+ let(:expected) do
115
+ selector.merge(:$readPreference => expected_read_preference)
116
+ end
117
+
118
+ it 'adds read preference' do
119
+ expect(actual).to eq(expected)
120
+ end
121
+ end
122
+ end
123
+ end
124
+
102
125
  shared_examples_for 'sends user-specified read preference' do
103
126
  %i(primary primary_preferred secondary secondary_preferred nearest).each do |_mode|
104
127
  active_mode = _mode
@@ -302,7 +325,7 @@ describe Mongo::Operation::SessionsSupported do
302
325
  let(:standalone?) { false }
303
326
  let(:mongos?) { false }
304
327
 
305
- it_behaves_like 'sends user-specified read preference'
328
+ it_behaves_like 'sends read preference correctly for replica set'
306
329
  end
307
330
  end
308
331
  end