mongo 2.16.0 → 2.17.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (119) 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/cluster/reapers/cursor_reaper.rb +26 -14
  7. data/lib/mongo/collection/view/aggregation.rb +62 -17
  8. data/lib/mongo/collection/view/builder/aggregation.rb +11 -13
  9. data/lib/mongo/collection/view/builder/map_reduce.rb +8 -5
  10. data/lib/mongo/collection/view/change_stream.rb +7 -3
  11. data/lib/mongo/collection/view/iterable.rb +2 -3
  12. data/lib/mongo/collection/view/map_reduce.rb +16 -1
  13. data/lib/mongo/collection/view/readable.rb +24 -1
  14. data/lib/mongo/collection/view/writable.rb +23 -0
  15. data/lib/mongo/collection.rb +21 -1
  16. data/lib/mongo/cursor/kill_spec.rb +19 -2
  17. data/lib/mongo/cursor.rb +5 -5
  18. data/lib/mongo/database/view.rb +4 -2
  19. data/lib/mongo/database.rb +6 -6
  20. data/lib/mongo/error/snapshot_session_invalid_server_version.rb +31 -0
  21. data/lib/mongo/error/snapshot_session_transaction_prohibited.rb +30 -0
  22. data/lib/mongo/error.rb +2 -0
  23. data/lib/mongo/operation/delete/op_msg.rb +2 -1
  24. data/lib/mongo/operation/find/builder/command.rb +1 -0
  25. data/lib/mongo/operation/result.rb +6 -0
  26. data/lib/mongo/operation/shared/executable.rb +4 -0
  27. data/lib/mongo/operation/shared/sessions_supported.rb +18 -2
  28. data/lib/mongo/operation/update/op_msg.rb +2 -1
  29. data/lib/mongo/server/description/features.rb +3 -1
  30. data/lib/mongo/server/push_monitor.rb +4 -1
  31. data/lib/mongo/server_selector/base.rb +26 -4
  32. data/lib/mongo/session.rb +19 -0
  33. data/lib/mongo/socket/ocsp_cache.rb +2 -3
  34. data/lib/mongo/socket.rb +1 -5
  35. data/lib/mongo/utils.rb +0 -6
  36. data/lib/mongo/version.rb +1 -1
  37. data/mongo.gemspec +1 -1
  38. data/spec/integration/read_preference_spec.rb +16 -12
  39. data/spec/lite_spec_helper.rb +7 -0
  40. data/spec/mongo/cluster/cursor_reaper_spec.rb +22 -15
  41. data/spec/mongo/collection/view/aggregation_spec.rb +71 -95
  42. data/spec/mongo/collection/view/change_stream_spec.rb +1 -1
  43. data/spec/mongo/collection/view/map_reduce_spec.rb +30 -1
  44. data/spec/mongo/cursor_spec.rb +3 -2
  45. data/spec/mongo/operation/read_preference_op_msg_spec.rb +24 -1
  46. data/spec/mongo/server/monitor/connection_spec.rb +22 -0
  47. data/spec/mongo/server/push_monitor_spec.rb +101 -0
  48. data/spec/mongo/server_selector_spec.rb +136 -15
  49. data/spec/mongo/socket/ssl_spec.rb +26 -58
  50. data/spec/mongo/utils_spec.rb +0 -14
  51. data/spec/runners/auth.rb +1 -1
  52. data/spec/runners/change_streams/spec.rb +1 -1
  53. data/spec/runners/cmap.rb +1 -1
  54. data/spec/runners/command_monitoring.rb +1 -1
  55. data/spec/runners/connection_string.rb +1 -1
  56. data/spec/runners/crud/spec.rb +1 -3
  57. data/spec/runners/crud/verifier.rb +1 -2
  58. data/spec/runners/gridfs.rb +1 -1
  59. data/spec/runners/read_write_concern_document.rb +1 -1
  60. data/spec/runners/sdam.rb +1 -1
  61. data/spec/runners/server_selection.rb +1 -1
  62. data/spec/runners/server_selection_rtt.rb +1 -1
  63. data/spec/runners/unified/assertions.rb +3 -1
  64. data/spec/runners/unified/crud_operations.rb +77 -23
  65. data/spec/runners/unified/ddl_operations.rb +29 -1
  66. data/spec/runners/unified/entity_map.rb +3 -3
  67. data/spec/runners/unified/support_operations.rb +6 -1
  68. data/spec/runners/unified/test.rb +15 -3
  69. data/spec/runners/unified/test_group.rb +1 -1
  70. data/spec/spec_tests/data/crud_unified/aggregate-let.yml +138 -0
  71. data/spec/spec_tests/data/crud_unified/aggregate-write-readPreference.yml +155 -0
  72. data/spec/spec_tests/data/crud_unified/db-aggregate-write-readPreference.yml +151 -0
  73. data/spec/spec_tests/data/crud_unified/deleteMany-let.yml +91 -0
  74. data/spec/spec_tests/data/crud_unified/deleteOne-let.yml +89 -0
  75. data/spec/spec_tests/data/crud_unified/find-let.yml +71 -0
  76. data/spec/spec_tests/data/crud_unified/findOneAndDelete-let.yml +88 -0
  77. data/spec/spec_tests/data/crud_unified/findOneAndReplace-let.yml +94 -0
  78. data/spec/spec_tests/data/crud_unified/findOneAndUpdate-let.yml +96 -0
  79. data/spec/spec_tests/data/crud_unified/updateMany-let.yml +103 -0
  80. data/spec/spec_tests/data/crud_unified/updateOne-let.yml +98 -0
  81. data/spec/spec_tests/data/max_staleness/ReplicaSetNoPrimary/DefaultNoMaxStaleness.yml +2 -2
  82. data/spec/spec_tests/data/max_staleness/ReplicaSetNoPrimary/LastUpdateTime.yml +3 -3
  83. data/spec/spec_tests/data/max_staleness/ReplicaSetNoPrimary/Nearest.yml +3 -3
  84. data/spec/spec_tests/data/max_staleness/ReplicaSetNoPrimary/Nearest2.yml +3 -3
  85. data/spec/spec_tests/data/max_staleness/ReplicaSetNoPrimary/PrimaryPreferred.yml +2 -2
  86. data/spec/spec_tests/data/max_staleness/ReplicaSetNoPrimary/PrimaryPreferred_tags.yml +2 -2
  87. data/spec/spec_tests/data/max_staleness/ReplicaSetNoPrimary/Secondary.yml +4 -4
  88. data/spec/spec_tests/data/max_staleness/ReplicaSetNoPrimary/SecondaryPreferred.yml +2 -2
  89. data/spec/spec_tests/data/max_staleness/ReplicaSetNoPrimary/SecondaryPreferred_tags.yml +4 -4
  90. data/spec/spec_tests/data/max_staleness/ReplicaSetNoPrimary/ZeroMaxStaleness.yml +2 -2
  91. data/spec/spec_tests/data/max_staleness/ReplicaSetWithPrimary/DefaultNoMaxStaleness.yml +2 -2
  92. data/spec/spec_tests/data/max_staleness/ReplicaSetWithPrimary/LastUpdateTime.yml +3 -3
  93. data/spec/spec_tests/data/max_staleness/ReplicaSetWithPrimary/LongHeartbeat.yml +2 -2
  94. data/spec/spec_tests/data/max_staleness/ReplicaSetWithPrimary/LongHeartbeat2.yml +2 -2
  95. data/spec/spec_tests/data/max_staleness/ReplicaSetWithPrimary/MaxStalenessTooSmall.yml +2 -2
  96. data/spec/spec_tests/data/max_staleness/ReplicaSetWithPrimary/MaxStalenessWithModePrimary.yml +2 -2
  97. data/spec/spec_tests/data/max_staleness/ReplicaSetWithPrimary/Nearest.yml +3 -3
  98. data/spec/spec_tests/data/max_staleness/ReplicaSetWithPrimary/Nearest2.yml +3 -3
  99. data/spec/spec_tests/data/max_staleness/ReplicaSetWithPrimary/Nearest_tags.yml +2 -2
  100. data/spec/spec_tests/data/max_staleness/ReplicaSetWithPrimary/PrimaryPreferred.yml +2 -2
  101. data/spec/spec_tests/data/max_staleness/ReplicaSetWithPrimary/SecondaryPreferred.yml +2 -2
  102. data/spec/spec_tests/data/max_staleness/ReplicaSetWithPrimary/SecondaryPreferred_tags.yml +5 -5
  103. data/spec/spec_tests/data/max_staleness/ReplicaSetWithPrimary/SecondaryPreferred_tags2.yml +3 -3
  104. data/spec/spec_tests/data/max_staleness/ReplicaSetWithPrimary/Secondary_tags.yml +5 -5
  105. data/spec/spec_tests/data/max_staleness/ReplicaSetWithPrimary/Secondary_tags2.yml +3 -3
  106. data/spec/spec_tests/data/max_staleness/ReplicaSetWithPrimary/ZeroMaxStaleness.yml +2 -2
  107. data/spec/spec_tests/data/max_staleness/Sharded/SmallMaxStaleness.yml +2 -2
  108. data/spec/spec_tests/data/max_staleness/Single/SmallMaxStaleness.yml +1 -1
  109. data/spec/spec_tests/data/max_staleness/Unknown/SmallMaxStaleness.yml +1 -1
  110. data/spec/spec_tests/data/sessions_unified/snapshot-sessions-not-supported-client-error.yml +69 -0
  111. data/spec/spec_tests/data/sessions_unified/snapshot-sessions-not-supported-server-error.yml +102 -0
  112. data/spec/spec_tests/data/sessions_unified/snapshot-sessions-unsupported-ops.yml +258 -0
  113. data/spec/spec_tests/data/sessions_unified/snapshot-sessions.yml +482 -0
  114. data/spec/spec_tests/seed_list_discovery_spec.rb +1 -1
  115. data/spec/spec_tests/sessions_unified_spec.rb +13 -0
  116. data/spec/support/utils.rb +31 -0
  117. data.tar.gz.sig +0 -0
  118. metadata +1127 -1090
  119. metadata.gz.sig +1 -2
@@ -25,14 +25,31 @@ module Mongo
25
25
  # @api private
26
26
  class KillSpec
27
27
 
28
- def initialize(cursor_id:, coll_name:, db_name:, service_id:)
28
+ def initialize(cursor_id:, coll_name:, db_name:, service_id:, server_address:)
29
29
  @cursor_id = cursor_id
30
30
  @coll_name = coll_name
31
31
  @db_name = db_name
32
32
  @service_id = service_id
33
+ @server_address = server_address
33
34
  end
34
35
 
35
- attr_reader :cursor_id, :coll_name, :db_name, :service_id
36
+ attr_reader :cursor_id, :coll_name, :db_name, :service_id, :server_address
37
+
38
+ def ==(other)
39
+ cursor_id == other.cursor_id &&
40
+ coll_name == other.coll_name &&
41
+ db_name == other.db_name &&
42
+ service_id == other.service_id &&
43
+ server_address == other.server_address
44
+ end
45
+
46
+ def eql?(other)
47
+ self.==(other)
48
+ end
49
+
50
+ def hash
51
+ [cursor_id, coll_name, db_name, service_id, server_address].compact.hash
52
+ end
36
53
  end
37
54
  end
38
55
  end
data/lib/mongo/cursor.rb CHANGED
@@ -84,9 +84,8 @@ module Mongo
84
84
  @session = @options[:session]
85
85
  unless closed?
86
86
  register
87
- ObjectSpace.define_finalizer(self, self.class.finalize(kill_spec,
87
+ ObjectSpace.define_finalizer(self, self.class.finalize(kill_spec(server),
88
88
  cluster,
89
- server,
90
89
  @session))
91
90
  end
92
91
  end
@@ -107,12 +106,12 @@ module Mongo
107
106
  # @return [ Proc ] The Finalizer.
108
107
  #
109
108
  # @api private
110
- def self.finalize(kill_spec, cluster, server, session)
109
+ def self.finalize(kill_spec, cluster, session)
111
110
  unless KillSpec === kill_spec
112
111
  raise ArgumentError, "First argument must be a KillSpec: #{kill_spec.inspect}"
113
112
  end
114
113
  proc do
115
- cluster.schedule_kill_cursor(kill_spec, server)
114
+ cluster.schedule_kill_cursor(kill_spec)
116
115
  session.end_session if session && session.implicit?
117
116
  end
118
117
  end
@@ -367,12 +366,13 @@ module Mongo
367
366
  end
368
367
 
369
368
  # @api private
370
- def kill_spec
369
+ def kill_spec(server)
371
370
  KillSpec.new(
372
371
  cursor_id: id,
373
372
  coll_name: collection_name,
374
373
  db_name: database.name,
375
374
  service_id: initial_result.connection_description.service_id,
375
+ server_address: server.address,
376
376
  )
377
377
  end
378
378
 
@@ -28,7 +28,7 @@ module Mongo
28
28
 
29
29
  def_delegators :@database, :cluster, :read_preference, :client
30
30
  # @api private
31
- def_delegators :@database, :server_selector, :read_concern
31
+ def_delegators :@database, :server_selector, :read_concern, :write_concern
32
32
  def_delegators :cluster, :next_primary
33
33
 
34
34
  # @return [ Integer ] batch_size The size of the batch of results
@@ -57,6 +57,7 @@ module Mongo
57
57
  #
58
58
  # See https://docs.mongodb.com/manual/reference/command/listCollections/
59
59
  # for more information and usage.
60
+ # @option options [ Session ] :session The session to use.
60
61
  #
61
62
  # @return [ Array<String> ] The names of all non-system collections.
62
63
  #
@@ -100,12 +101,13 @@ module Mongo
100
101
  #
101
102
  # See https://docs.mongodb.com/manual/reference/command/listCollections/
102
103
  # for more information and usage.
104
+ # @option options [ Session ] :session The session to use.
103
105
  #
104
106
  # @return [ Array<Hash> ] Info for each collection in the database.
105
107
  #
106
108
  # @since 2.0.5
107
109
  def list_collections(options = {})
108
- session = client.send(:get_session)
110
+ session = client.send(:get_session, options)
109
111
  collections_info(session, ServerSelector.primary, options)
110
112
  end
111
113
 
@@ -253,12 +253,12 @@ module Mongo
253
253
 
254
254
  client.send(:with_session, opts) do |session|
255
255
  read_with_retry(session, preference) do |server|
256
- Operation::Command.new({
257
- :selector => operation.dup,
258
- :db_name => name,
259
- :read => preference,
260
- :session => session
261
- }).execute(server, context: Operation::Context.new(client: client, session: session))
256
+ Operation::Command.new(
257
+ selector: operation.dup,
258
+ db_name: name,
259
+ read: preference,
260
+ session: session,
261
+ ).execute(server, context: Operation::Context.new(client: client, session: session))
262
262
  end
263
263
  end
264
264
  end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+ # encoding: utf-8
3
+
4
+ # Copyright (C) 2021 MongoDB Inc.
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License");
7
+ # you may not use this file except in compliance with the License.
8
+ # You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS,
14
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ # See the License for the specific language governing permissions and
16
+ # limitations under the License.
17
+
18
+ module Mongo
19
+ class Error
20
+
21
+ # Exception raised if an operation using a snapshot session is
22
+ # directed to a pre-5.0 server.
23
+ class SnapshotSessionInvalidServerVersion < Error
24
+
25
+ # Instantiate the new exception.
26
+ def initialize
27
+ super("Snapshot reads require MongoDB 5.0 or later")
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+ # encoding: utf-8
3
+
4
+ # Copyright (C) 2021 MongoDB Inc.
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License");
7
+ # you may not use this file except in compliance with the License.
8
+ # You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS,
14
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ # See the License for the specific language governing permissions and
16
+ # limitations under the License.
17
+
18
+ module Mongo
19
+ class Error
20
+
21
+ # Exception raised if a transaction is attempted on a snapshot session.
22
+ class SnapshotSessionTransactionProhibited < Error
23
+
24
+ # Instantiate the new exception.
25
+ def initialize
26
+ super("Transactions are not supported in snapshot sessions")
27
+ end
28
+ end
29
+ end
30
+ end
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.1'.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
@@ -157,6 +157,13 @@ RSpec.configure do |config|
157
157
  end
158
158
 
159
159
  if SpecConfig.instance.active_support?
160
+ require "active_support/version"
161
+ if ActiveSupport.version >= Gem::Version.new(7)
162
+ # ActiveSupport wants us to require ALL of it all of the time.
163
+ # See: https://github.com/rails/rails/issues/43851,
164
+ # https://github.com/rails/rails/issues/43889, etc.
165
+ require 'active_support'
166
+ end
160
167
  require "active_support/time"
161
168
  require 'mongo/active_support'
162
169
  end
@@ -41,12 +41,12 @@ describe Mongo::Cluster::CursorReaper do
41
41
  let(:cursor_id) { 1 }
42
42
  let(:cursor_kill_spec_1) do
43
43
  Mongo::Cursor::KillSpec.new(
44
- cursor_id: cursor_id, coll_name: 'c', db_name: 'd', service_id: nil,
44
+ cursor_id: cursor_id, coll_name: 'c', db_name: 'd', service_id: nil, server_address: address
45
45
  )
46
46
  end
47
47
  let(:cursor_kill_spec_2) do
48
48
  Mongo::Cursor::KillSpec.new(
49
- cursor_id: cursor_id, coll_name: 'c', db_name: 'q', service_id: nil,
49
+ cursor_id: cursor_id, coll_name: 'c', db_name: 'q', service_id: nil, server_address: address
50
50
  )
51
51
  end
52
52
  let(:to_kill) { reaper.instance_variable_get(:@to_kill)}
@@ -60,36 +60,40 @@ describe Mongo::Cluster::CursorReaper do
60
60
  context 'when there is not a list already for the server' do
61
61
 
62
62
  before do
63
- reaper.schedule_kill_cursor(cursor_kill_spec_1, server)
63
+ reaper.schedule_kill_cursor(cursor_kill_spec_1)
64
+ reaper.read_scheduled_kill_specs
64
65
  end
65
66
 
66
67
  it 'initializes the list of op specs to a set' do
67
- expect(to_kill.keys).to eq([ address.seed ])
68
- expect(to_kill[address.seed]).to eq(Set.new([cursor_kill_spec_1]))
68
+ expect(to_kill.keys).to eq([ address ])
69
+ expect(to_kill[address]).to contain_exactly(cursor_kill_spec_1)
69
70
  end
70
71
  end
71
72
 
72
73
  context 'when there is a list of ops already for the server' do
73
74
 
74
75
  before do
75
- reaper.schedule_kill_cursor(cursor_kill_spec_1, server)
76
- reaper.schedule_kill_cursor(cursor_kill_spec_2, server)
76
+ reaper.schedule_kill_cursor(cursor_kill_spec_1)
77
+ reaper.read_scheduled_kill_specs
78
+ reaper.schedule_kill_cursor(cursor_kill_spec_2)
79
+ reaper.read_scheduled_kill_specs
77
80
  end
78
81
 
79
82
  it 'adds the op to the server list' do
80
- expect(to_kill.keys).to eq([ address.seed ])
81
- expect(to_kill[address.seed]).to contain_exactly(cursor_kill_spec_1, cursor_kill_spec_2)
83
+ expect(to_kill.keys).to eq([ address ])
84
+ expect(to_kill[address]).to contain_exactly(cursor_kill_spec_1, cursor_kill_spec_2)
82
85
  end
83
86
 
84
87
  context 'when the same op is added more than once' do
85
88
 
86
89
  before do
87
- reaper.schedule_kill_cursor(cursor_kill_spec_2, server)
90
+ reaper.schedule_kill_cursor(cursor_kill_spec_2)
91
+ reaper.read_scheduled_kill_specs
88
92
  end
89
93
 
90
94
  it 'does not allow duplicates ops for a server' do
91
- expect(to_kill.keys).to eq([ address.seed ])
92
- expect(to_kill[address.seed]).to contain_exactly(cursor_kill_spec_1, cursor_kill_spec_2)
95
+ expect(to_kill.keys).to eq([ address ])
96
+ expect(to_kill[address]).to contain_exactly(cursor_kill_spec_1, cursor_kill_spec_2)
93
97
  end
94
98
  end
95
99
  end
@@ -98,7 +102,7 @@ describe Mongo::Cluster::CursorReaper do
98
102
  context 'when the cursor is not on the list of active cursors' do
99
103
 
100
104
  before do
101
- reaper.schedule_kill_cursor(cursor_kill_spec_1, server)
105
+ reaper.schedule_kill_cursor(cursor_kill_spec_1)
102
106
  end
103
107
 
104
108
  it 'does not add the kill cursors op spec to the list' do
@@ -189,8 +193,11 @@ describe Mongo::Cluster::CursorReaper do
189
193
  around do |example|
190
194
  authorized_collection.insert_many(docs)
191
195
  periodic_executor.stop!
192
- cluster.schedule_kill_cursor(cursor.kill_spec,
193
- cursor.instance_variable_get(:@server))
196
+ cluster.schedule_kill_cursor(
197
+ cursor.kill_spec(
198
+ cursor.instance_variable_get(:@server)
199
+ )
200
+ )
194
201
  periodic_executor.flush
195
202
  example.run
196
203
  periodic_executor.run!