mongo 2.16.0 → 2.17.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data/README.md +1 -1
- data/lib/mongo/auth/aws/request.rb +0 -1
- data/lib/mongo/client.rb +4 -0
- data/lib/mongo/cluster/reapers/cursor_reaper.rb +26 -14
- data/lib/mongo/collection/view/aggregation.rb +62 -17
- data/lib/mongo/collection/view/builder/aggregation.rb +11 -13
- data/lib/mongo/collection/view/builder/map_reduce.rb +8 -5
- data/lib/mongo/collection/view/change_stream.rb +7 -3
- data/lib/mongo/collection/view/iterable.rb +2 -3
- data/lib/mongo/collection/view/map_reduce.rb +16 -1
- data/lib/mongo/collection/view/readable.rb +24 -1
- data/lib/mongo/collection/view/writable.rb +23 -0
- data/lib/mongo/collection.rb +21 -1
- data/lib/mongo/cursor/kill_spec.rb +19 -2
- data/lib/mongo/cursor.rb +5 -5
- data/lib/mongo/database/view.rb +4 -2
- data/lib/mongo/database.rb +6 -6
- data/lib/mongo/error/snapshot_session_invalid_server_version.rb +31 -0
- data/lib/mongo/error/snapshot_session_transaction_prohibited.rb +30 -0
- data/lib/mongo/error.rb +2 -0
- data/lib/mongo/operation/delete/op_msg.rb +2 -1
- data/lib/mongo/operation/find/builder/command.rb +1 -0
- data/lib/mongo/operation/result.rb +6 -0
- data/lib/mongo/operation/shared/executable.rb +4 -0
- data/lib/mongo/operation/shared/sessions_supported.rb +18 -2
- data/lib/mongo/operation/update/op_msg.rb +2 -1
- data/lib/mongo/server/description/features.rb +3 -1
- data/lib/mongo/server/push_monitor.rb +4 -1
- data/lib/mongo/server_selector/base.rb +26 -4
- data/lib/mongo/session.rb +19 -0
- data/lib/mongo/socket/ocsp_cache.rb +2 -3
- data/lib/mongo/socket.rb +1 -5
- data/lib/mongo/utils.rb +0 -6
- data/lib/mongo/version.rb +1 -1
- data/mongo.gemspec +1 -1
- data/spec/integration/read_preference_spec.rb +16 -12
- data/spec/lite_spec_helper.rb +7 -0
- data/spec/mongo/cluster/cursor_reaper_spec.rb +22 -15
- data/spec/mongo/collection/view/aggregation_spec.rb +71 -95
- data/spec/mongo/collection/view/change_stream_spec.rb +1 -1
- data/spec/mongo/collection/view/map_reduce_spec.rb +30 -1
- data/spec/mongo/cursor_spec.rb +3 -2
- data/spec/mongo/operation/read_preference_op_msg_spec.rb +24 -1
- data/spec/mongo/server/monitor/connection_spec.rb +22 -0
- data/spec/mongo/server/push_monitor_spec.rb +101 -0
- data/spec/mongo/server_selector_spec.rb +136 -15
- data/spec/mongo/socket/ssl_spec.rb +26 -58
- data/spec/mongo/utils_spec.rb +0 -14
- data/spec/runners/auth.rb +1 -1
- data/spec/runners/change_streams/spec.rb +1 -1
- data/spec/runners/cmap.rb +1 -1
- data/spec/runners/command_monitoring.rb +1 -1
- data/spec/runners/connection_string.rb +1 -1
- data/spec/runners/crud/spec.rb +1 -3
- data/spec/runners/crud/verifier.rb +1 -2
- data/spec/runners/gridfs.rb +1 -1
- data/spec/runners/read_write_concern_document.rb +1 -1
- data/spec/runners/sdam.rb +1 -1
- data/spec/runners/server_selection.rb +1 -1
- data/spec/runners/server_selection_rtt.rb +1 -1
- data/spec/runners/unified/assertions.rb +3 -1
- data/spec/runners/unified/crud_operations.rb +77 -23
- data/spec/runners/unified/ddl_operations.rb +29 -1
- data/spec/runners/unified/entity_map.rb +3 -3
- data/spec/runners/unified/support_operations.rb +6 -1
- data/spec/runners/unified/test.rb +15 -3
- data/spec/runners/unified/test_group.rb +1 -1
- data/spec/spec_tests/data/crud_unified/aggregate-let.yml +138 -0
- data/spec/spec_tests/data/crud_unified/aggregate-write-readPreference.yml +155 -0
- data/spec/spec_tests/data/crud_unified/db-aggregate-write-readPreference.yml +151 -0
- data/spec/spec_tests/data/crud_unified/deleteMany-let.yml +91 -0
- data/spec/spec_tests/data/crud_unified/deleteOne-let.yml +89 -0
- data/spec/spec_tests/data/crud_unified/find-let.yml +71 -0
- data/spec/spec_tests/data/crud_unified/findOneAndDelete-let.yml +88 -0
- data/spec/spec_tests/data/crud_unified/findOneAndReplace-let.yml +94 -0
- data/spec/spec_tests/data/crud_unified/findOneAndUpdate-let.yml +96 -0
- data/spec/spec_tests/data/crud_unified/updateMany-let.yml +103 -0
- data/spec/spec_tests/data/crud_unified/updateOne-let.yml +98 -0
- data/spec/spec_tests/data/max_staleness/ReplicaSetNoPrimary/DefaultNoMaxStaleness.yml +2 -2
- data/spec/spec_tests/data/max_staleness/ReplicaSetNoPrimary/LastUpdateTime.yml +3 -3
- data/spec/spec_tests/data/max_staleness/ReplicaSetNoPrimary/Nearest.yml +3 -3
- data/spec/spec_tests/data/max_staleness/ReplicaSetNoPrimary/Nearest2.yml +3 -3
- data/spec/spec_tests/data/max_staleness/ReplicaSetNoPrimary/PrimaryPreferred.yml +2 -2
- data/spec/spec_tests/data/max_staleness/ReplicaSetNoPrimary/PrimaryPreferred_tags.yml +2 -2
- data/spec/spec_tests/data/max_staleness/ReplicaSetNoPrimary/Secondary.yml +4 -4
- data/spec/spec_tests/data/max_staleness/ReplicaSetNoPrimary/SecondaryPreferred.yml +2 -2
- data/spec/spec_tests/data/max_staleness/ReplicaSetNoPrimary/SecondaryPreferred_tags.yml +4 -4
- data/spec/spec_tests/data/max_staleness/ReplicaSetNoPrimary/ZeroMaxStaleness.yml +2 -2
- data/spec/spec_tests/data/max_staleness/ReplicaSetWithPrimary/DefaultNoMaxStaleness.yml +2 -2
- data/spec/spec_tests/data/max_staleness/ReplicaSetWithPrimary/LastUpdateTime.yml +3 -3
- data/spec/spec_tests/data/max_staleness/ReplicaSetWithPrimary/LongHeartbeat.yml +2 -2
- data/spec/spec_tests/data/max_staleness/ReplicaSetWithPrimary/LongHeartbeat2.yml +2 -2
- data/spec/spec_tests/data/max_staleness/ReplicaSetWithPrimary/MaxStalenessTooSmall.yml +2 -2
- data/spec/spec_tests/data/max_staleness/ReplicaSetWithPrimary/MaxStalenessWithModePrimary.yml +2 -2
- data/spec/spec_tests/data/max_staleness/ReplicaSetWithPrimary/Nearest.yml +3 -3
- data/spec/spec_tests/data/max_staleness/ReplicaSetWithPrimary/Nearest2.yml +3 -3
- data/spec/spec_tests/data/max_staleness/ReplicaSetWithPrimary/Nearest_tags.yml +2 -2
- data/spec/spec_tests/data/max_staleness/ReplicaSetWithPrimary/PrimaryPreferred.yml +2 -2
- data/spec/spec_tests/data/max_staleness/ReplicaSetWithPrimary/SecondaryPreferred.yml +2 -2
- data/spec/spec_tests/data/max_staleness/ReplicaSetWithPrimary/SecondaryPreferred_tags.yml +5 -5
- data/spec/spec_tests/data/max_staleness/ReplicaSetWithPrimary/SecondaryPreferred_tags2.yml +3 -3
- data/spec/spec_tests/data/max_staleness/ReplicaSetWithPrimary/Secondary_tags.yml +5 -5
- data/spec/spec_tests/data/max_staleness/ReplicaSetWithPrimary/Secondary_tags2.yml +3 -3
- data/spec/spec_tests/data/max_staleness/ReplicaSetWithPrimary/ZeroMaxStaleness.yml +2 -2
- data/spec/spec_tests/data/max_staleness/Sharded/SmallMaxStaleness.yml +2 -2
- data/spec/spec_tests/data/max_staleness/Single/SmallMaxStaleness.yml +1 -1
- data/spec/spec_tests/data/max_staleness/Unknown/SmallMaxStaleness.yml +1 -1
- data/spec/spec_tests/data/sessions_unified/snapshot-sessions-not-supported-client-error.yml +69 -0
- data/spec/spec_tests/data/sessions_unified/snapshot-sessions-not-supported-server-error.yml +102 -0
- data/spec/spec_tests/data/sessions_unified/snapshot-sessions-unsupported-ops.yml +258 -0
- data/spec/spec_tests/data/sessions_unified/snapshot-sessions.yml +482 -0
- data/spec/spec_tests/seed_list_discovery_spec.rb +1 -1
- data/spec/spec_tests/sessions_unified_spec.rb +13 -0
- data/spec/support/utils.rb +31 -0
- data.tar.gz.sig +0 -0
- metadata +1127 -1090
- 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,
|
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
|
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
|
|
data/lib/mongo/database/view.rb
CHANGED
@@ -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
|
|
data/lib/mongo/database.rb
CHANGED
@@ -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
|
-
:
|
258
|
-
:
|
259
|
-
:
|
260
|
-
:
|
261
|
-
|
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
|
-
|
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
|
@@ -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
|
@@ -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,
|
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)
|
@@ -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 = (
|
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 =
|
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
|
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
|
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
|
-
|
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
data/mongo.gemspec
CHANGED
@@ -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
|
-
|
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
|
-
|
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
|
data/spec/lite_spec_helper.rb
CHANGED
@@ -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
|
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
|
68
|
-
expect(to_kill[address
|
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
|
76
|
-
reaper.
|
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
|
81
|
-
expect(to_kill[address
|
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
|
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
|
92
|
-
expect(to_kill[address
|
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
|
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(
|
193
|
-
|
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!
|