mongo 2.24.0 → 2.25.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.
- checksums.yaml +4 -4
- data/bin/mongo_console +2 -2
- data/lib/mongo/address/validator.rb +1 -1
- data/lib/mongo/address.rb +1 -3
- data/lib/mongo/auth/aws/conversation.rb +0 -4
- data/lib/mongo/auth/aws/credentials_retriever.rb +3 -8
- data/lib/mongo/auth/base.rb +1 -1
- data/lib/mongo/auth/sasl_conversation_base.rb +1 -1
- data/lib/mongo/auth/scram_conversation_base.rb +1 -8
- data/lib/mongo/auth/stringprep.rb +1 -6
- data/lib/mongo/auth/user/view.rb +1 -1
- data/lib/mongo/bulk_write/result.rb +10 -2
- data/lib/mongo/bulk_write/result_combiner.rb +11 -4
- data/lib/mongo/bulk_write.rb +1 -1
- data/lib/mongo/client.rb +82 -12
- data/lib/mongo/cluster/reapers/cursor_reaper.rb +12 -5
- data/lib/mongo/cluster/sdam_flow.rb +4 -4
- data/lib/mongo/cluster/topology/base.rb +1 -1
- data/lib/mongo/cluster.rb +12 -28
- data/lib/mongo/collection/view/aggregation/behavior.rb +1 -1
- data/lib/mongo/collection/view/aggregation.rb +2 -4
- data/lib/mongo/collection/view/builder/aggregation.rb +3 -5
- data/lib/mongo/collection/view/builder/map_reduce.rb +1 -1
- data/lib/mongo/collection/view/change_stream.rb +14 -9
- data/lib/mongo/collection/view/map_reduce.rb +2 -11
- data/lib/mongo/collection/view/readable.rb +11 -7
- data/lib/mongo/collection/view/writable.rb +4 -4
- data/lib/mongo/collection/view.rb +8 -4
- data/lib/mongo/collection.rb +1 -1
- data/lib/mongo/config.rb +5 -0
- data/lib/mongo/crypt/auto_encrypter.rb +1 -1
- data/lib/mongo/crypt/binary.rb +7 -4
- data/lib/mongo/crypt/binding.rb +88 -10
- data/lib/mongo/crypt/context.rb +50 -19
- data/lib/mongo/crypt/encryption_io.rb +11 -5
- data/lib/mongo/crypt/handle.rb +17 -5
- data/lib/mongo/crypt/kms/credentials.rb +70 -31
- data/lib/mongo/crypt/kms/kmip/master_document.rb +30 -1
- data/lib/mongo/crypt/kms/master_key_document.rb +11 -6
- data/lib/mongo/crypt/kms.rb +12 -1
- data/lib/mongo/cursor.rb +8 -2
- data/lib/mongo/database/cursor_command_view.rb +95 -0
- data/lib/mongo/database.rb +144 -18
- data/lib/mongo/error/bulk_write_error.rb +29 -1
- data/lib/mongo/error/invalid_server_preference.rb +3 -0
- data/lib/mongo/error/invalid_uri.rb +5 -1
- data/lib/mongo/error/operation_failure.rb +42 -3
- data/lib/mongo/error/parser.rb +3 -3
- data/lib/mongo/error/sdam_error_detection.rb +2 -3
- data/lib/mongo/grid/fs_bucket.rb +27 -21
- data/lib/mongo/grid/stream/write.rb +2 -2
- data/lib/mongo/index/view.rb +13 -7
- data/lib/mongo/monitoring/event/cmap/connection_check_out_failed.rb +1 -0
- data/lib/mongo/monitoring/event/cmap/connection_check_out_started.rb +1 -0
- data/lib/mongo/monitoring/event/cmap/connection_checked_in.rb +1 -0
- data/lib/mongo/monitoring/event/cmap/connection_checked_out.rb +1 -0
- data/lib/mongo/monitoring/event/cmap/connection_closed.rb +1 -0
- data/lib/mongo/monitoring/event/cmap/connection_created.rb +1 -0
- data/lib/mongo/monitoring/event/cmap/connection_ready.rb +1 -0
- data/lib/mongo/monitoring/event/cmap/pool_cleared.rb +1 -0
- data/lib/mongo/monitoring/event/cmap/pool_closed.rb +1 -0
- data/lib/mongo/monitoring/event/cmap/pool_created.rb +1 -0
- data/lib/mongo/monitoring/event/cmap/pool_ready.rb +1 -0
- data/lib/mongo/monitoring/event/command_failed.rb +1 -0
- data/lib/mongo/monitoring/event/command_started.rb +1 -0
- data/lib/mongo/monitoring/event/command_succeeded.rb +1 -0
- data/lib/mongo/monitoring/event/secure.rb +12 -17
- data/lib/mongo/monitoring/event/server_closed.rb +1 -0
- data/lib/mongo/monitoring/event/server_description_changed.rb +1 -0
- data/lib/mongo/monitoring/event/server_heartbeat_failed.rb +1 -0
- data/lib/mongo/monitoring/event/server_heartbeat_started.rb +1 -0
- data/lib/mongo/monitoring/event/server_heartbeat_succeeded.rb +1 -0
- data/lib/mongo/monitoring/event/server_opening.rb +1 -0
- data/lib/mongo/monitoring/event/topology_changed.rb +1 -0
- data/lib/mongo/monitoring/event/topology_closed.rb +1 -0
- data/lib/mongo/monitoring/event/topology_opening.rb +1 -0
- data/lib/mongo/monitoring/server_description_changed_log_subscriber.rb +2 -0
- data/lib/mongo/monitoring/topology_changed_log_subscriber.rb +3 -0
- data/lib/mongo/operation/create/op_msg.rb +1 -0
- data/lib/mongo/operation/create_index/op_msg.rb +2 -1
- data/lib/mongo/operation/cursor_command/op_msg.rb +37 -0
- data/lib/mongo/operation/cursor_command/result.rb +60 -0
- data/lib/mongo/operation/cursor_command.rb +33 -0
- data/lib/mongo/operation/delete/op_msg.rb +1 -0
- data/lib/mongo/operation/drop/op_msg.rb +1 -0
- data/lib/mongo/operation/drop_database/op_msg.rb +1 -0
- data/lib/mongo/operation/drop_index/op_msg.rb +1 -0
- data/lib/mongo/operation/find/builder/command.rb +1 -1
- data/lib/mongo/operation/insert/bulk_result.rb +2 -4
- data/lib/mongo/operation/insert/op_msg.rb +1 -0
- data/lib/mongo/operation/result.rb +14 -26
- data/lib/mongo/operation/shared/causal_consistency_supported.rb +4 -2
- data/lib/mongo/operation/shared/executable.rb +2 -10
- data/lib/mongo/operation/shared/result/aggregatable.rb +2 -2
- data/lib/mongo/operation/shared/sessions_supported.rb +3 -5
- data/lib/mongo/operation/update/bulk_result.rb +1 -1
- data/lib/mongo/operation/update/op_msg.rb +1 -0
- data/lib/mongo/operation/write_command/op_msg.rb +2 -0
- data/lib/mongo/operation.rb +1 -0
- data/lib/mongo/options/redacted.rb +2 -2
- data/lib/mongo/protocol/compressed.rb +15 -11
- data/lib/mongo/protocol/message.rb +16 -16
- data/lib/mongo/protocol/registry.rb +1 -1
- data/lib/mongo/protocol/serializers.rb +1 -3
- data/lib/mongo/query_cache.rb +3 -5
- data/lib/mongo/server/connection.rb +3 -1
- data/lib/mongo/server/connection_common.rb +9 -1
- data/lib/mongo/server/connection_pool/generation_manager.rb +1 -1
- data/lib/mongo/server/connection_pool.rb +79 -18
- data/lib/mongo/server/description/features.rb +2 -2
- data/lib/mongo/server/description.rb +24 -2
- data/lib/mongo/server/monitor/connection.rb +3 -2
- data/lib/mongo/server/monitor.rb +133 -30
- data/lib/mongo/server/pending_connection.rb +1 -0
- data/lib/mongo/server/push_monitor.rb +16 -2
- data/lib/mongo/server.rb +27 -7
- data/lib/mongo/server_selector/base.rb +16 -6
- data/lib/mongo/server_selector/nearest.rb +3 -0
- data/lib/mongo/server_selector/primary.rb +3 -0
- data/lib/mongo/server_selector/primary_preferred.rb +3 -0
- data/lib/mongo/server_selector/secondary.rb +3 -0
- data/lib/mongo/server_selector/secondary_preferred.rb +7 -1
- data/lib/mongo/session.rb +56 -24
- data/lib/mongo/socket/ssl.rb +3 -3
- data/lib/mongo/socket.rb +1 -7
- data/lib/mongo/srv/monitor.rb +11 -2
- data/lib/mongo/srv/resolver.rb +1 -1
- data/lib/mongo/srv/result.rb +5 -3
- data/lib/mongo/timeout.rb +10 -15
- data/lib/mongo/tracing/open_telemetry/command_tracer.rb +43 -2
- data/lib/mongo/uri/options_mapper.rb +21 -9
- data/lib/mongo/uri/srv_protocol.rb +4 -4
- data/lib/mongo/uri.rb +56 -8
- data/lib/mongo/utils.rb +1 -1
- data/lib/mongo/version.rb +1 -1
- data/lib/mongo.rb +1 -0
- metadata +6 -4
- data/lib/mongo/auth/stringprep/unicode_normalize/normalize.rb +0 -173
- data/lib/mongo/auth/stringprep/unicode_normalize/tables.rb +0 -1164
|
@@ -21,28 +21,17 @@ module Mongo
|
|
|
21
21
|
#
|
|
22
22
|
# @api private
|
|
23
23
|
class Credentials
|
|
24
|
-
#
|
|
25
|
-
|
|
24
|
+
# KMS provider types that support on-demand credential retrieval.
|
|
25
|
+
ON_DEMAND_PROVIDERS = %w[aws gcp azure].freeze
|
|
26
26
|
|
|
27
|
-
|
|
28
|
-
attr_reader :azure
|
|
27
|
+
attr_reader :credentials_map
|
|
29
28
|
|
|
30
|
-
#
|
|
31
|
-
attr_reader :gcp
|
|
32
|
-
|
|
33
|
-
# @return [ Credentials::KMIP | nil ] KMIP KMS credentials.
|
|
34
|
-
attr_reader :kmip
|
|
35
|
-
|
|
36
|
-
# @return [ Credentials::Local | nil ] Local KMS credentials.
|
|
37
|
-
attr_reader :local
|
|
38
|
-
|
|
39
|
-
# Creates a KMS credentials object form a parameters hash.
|
|
29
|
+
# Creates a KMS credentials object from a parameters hash.
|
|
40
30
|
#
|
|
41
|
-
# @param [ Hash ] kms_providers A hash that contains
|
|
42
|
-
# KMS providers.
|
|
43
|
-
#
|
|
44
|
-
#
|
|
45
|
-
# classes inside Mongo::Crypt::KMS module.
|
|
31
|
+
# @param [ Hash ] kms_providers A hash that contains credentials for
|
|
32
|
+
# KMS providers. Keys may be provider types (:aws, :local, etc.) or
|
|
33
|
+
# named provider identifiers ("aws:name1", "local:name2", etc.).
|
|
34
|
+
# Values are hashes of credentials for the corresponding provider type.
|
|
46
35
|
#
|
|
47
36
|
# @note There may be more than one KMS provider specified.
|
|
48
37
|
#
|
|
@@ -51,29 +40,79 @@ module Mongo
|
|
|
51
40
|
def initialize(kms_providers)
|
|
52
41
|
raise ArgumentError.new('KMS providers options must not be nil') if kms_providers.nil?
|
|
53
42
|
|
|
54
|
-
@
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
43
|
+
@credentials_map = {}
|
|
44
|
+
|
|
45
|
+
kms_providers.each do |identifier, opts|
|
|
46
|
+
identifier_str = identifier.to_s
|
|
47
|
+
provider_type = KMS.provider_base_type(identifier_str)
|
|
48
|
+
|
|
49
|
+
creds = case provider_type
|
|
50
|
+
when 'aws' then AWS::Credentials.new(opts)
|
|
51
|
+
when 'azure' then Azure::Credentials.new(opts)
|
|
52
|
+
when 'gcp' then GCP::Credentials.new(opts)
|
|
53
|
+
when 'kmip' then KMIP::Credentials.new(opts)
|
|
54
|
+
when 'local' then Local::Credentials.new(opts)
|
|
55
|
+
else
|
|
56
|
+
raise ArgumentError.new(
|
|
57
|
+
'KMS providers options must have one of the following keys: ' \
|
|
58
|
+
':aws, :azure, :gcp, :kmip, :local'
|
|
59
|
+
)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
@credentials_map[identifier_str] = creds
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
return unless @credentials_map.empty?
|
|
60
66
|
|
|
61
67
|
raise ArgumentError.new(
|
|
62
|
-
'KMS providers options must have one of the following keys: '
|
|
68
|
+
'KMS providers options must have one of the following keys: ' \
|
|
63
69
|
':aws, :azure, :gcp, :kmip, :local'
|
|
64
70
|
)
|
|
65
71
|
end
|
|
66
72
|
|
|
73
|
+
# @return [ Credentials::AWS | nil ] AWS KMS credentials (unnamed provider only).
|
|
74
|
+
def aws
|
|
75
|
+
@credentials_map['aws']
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# @return [ Credentials::Azure | nil ] Azure KMS credentials (unnamed provider only).
|
|
79
|
+
def azure
|
|
80
|
+
@credentials_map['azure']
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# @return [ Credentials::GCP | nil ] GCP KMS credentials (unnamed provider only).
|
|
84
|
+
def gcp
|
|
85
|
+
@credentials_map['gcp']
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# @return [ Credentials::KMIP | nil ] KMIP KMS credentials (unnamed provider only).
|
|
89
|
+
def kmip
|
|
90
|
+
@credentials_map['kmip']
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# @return [ Credentials::Local | nil ] Local KMS credentials (unnamed provider only).
|
|
94
|
+
def local
|
|
95
|
+
@credentials_map['local']
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# Returns true if any configured provider supports on-demand credential
|
|
99
|
+
# retrieval and has been configured with empty credentials.
|
|
100
|
+
#
|
|
101
|
+
# @return [ Boolean ]
|
|
102
|
+
def any_on_demand?
|
|
103
|
+
@credentials_map.any? do |identifier, creds|
|
|
104
|
+
ON_DEMAND_PROVIDERS.include?(KMS.provider_base_type(identifier)) && creds.empty?
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
|
|
67
108
|
# Convert credentials object to a BSON document in libmongocrypt format.
|
|
68
109
|
#
|
|
69
110
|
# @return [ BSON::Document ] Credentials as BSON document.
|
|
70
111
|
def to_document
|
|
71
112
|
BSON::Document.new.tap do |bson|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
bson[:kmip] = @kmip.to_document if @kmip
|
|
76
|
-
bson[:local] = @local.to_document if @local
|
|
113
|
+
@credentials_map.each do |identifier, creds|
|
|
114
|
+
bson[identifier] = creds.to_document
|
|
115
|
+
end
|
|
77
116
|
end
|
|
78
117
|
end
|
|
79
118
|
end
|
|
@@ -31,8 +31,12 @@ module Mongo
|
|
|
31
31
|
# @return [ String | nil ] KMIP KMS endpoint with optional port.
|
|
32
32
|
attr_reader :endpoint
|
|
33
33
|
|
|
34
|
+
# @return [ true | false | nil ] Whether the KMIP server performs
|
|
35
|
+
# encryption and decryption of the data key.
|
|
36
|
+
attr_reader :delegated
|
|
37
|
+
|
|
34
38
|
FORMAT_HINT = 'KMIP KMS key document must be in the format: ' +
|
|
35
|
-
"{ key_id: 'KEY-ID', endpoint: 'ENDPOINT' }"
|
|
39
|
+
"{ key_id: 'KEY-ID', endpoint: 'ENDPOINT', delegated: true|false }"
|
|
36
40
|
|
|
37
41
|
# Creates a master key document object form a parameters hash.
|
|
38
42
|
#
|
|
@@ -42,6 +46,9 @@ module Mongo
|
|
|
42
46
|
# a 96 byte KMIP Secret Data managed object, optional. If key_id
|
|
43
47
|
# is omitted, the driver creates a random 96 byte identifier.
|
|
44
48
|
# @option opts [ String | nil ] :endpoint KMIP endpoint, optional.
|
|
49
|
+
# @option opts [ true | false | nil ] :delegated If true, the KMIP
|
|
50
|
+
# server performs encryption and decryption of the data key,
|
|
51
|
+
# optional. Defaults to false.
|
|
45
52
|
#
|
|
46
53
|
# @raise [ ArgumentError ] If required options are missing or incorrectly
|
|
47
54
|
# formatted.
|
|
@@ -52,6 +59,7 @@ module Mongo
|
|
|
52
59
|
@endpoint = validate_param(
|
|
53
60
|
:endpoint, opts, FORMAT_HINT, required: false
|
|
54
61
|
)
|
|
62
|
+
@delegated = validate_delegated(opts)
|
|
55
63
|
end
|
|
56
64
|
|
|
57
65
|
# Convert master key document object to a BSON document in libmongocrypt format.
|
|
@@ -63,8 +71,29 @@ module Mongo
|
|
|
63
71
|
}).tap do |bson|
|
|
64
72
|
bson.update({ endpoint: endpoint }) unless endpoint.nil?
|
|
65
73
|
bson.update({ keyId: key_id }) unless key_id.nil?
|
|
74
|
+
bson.update({ delegated: delegated }) unless delegated.nil?
|
|
66
75
|
end
|
|
67
76
|
end
|
|
77
|
+
|
|
78
|
+
private
|
|
79
|
+
|
|
80
|
+
# Validate the optional :delegated KMIP master key option.
|
|
81
|
+
#
|
|
82
|
+
# @param [ Hash ] opts Master key options.
|
|
83
|
+
#
|
|
84
|
+
# @return [ true | false | nil ] The delegated value, or nil if absent.
|
|
85
|
+
#
|
|
86
|
+
# @raise [ ArgumentError ] If delegated is present but not a boolean.
|
|
87
|
+
def validate_delegated(opts)
|
|
88
|
+
return nil unless opts.key?(:delegated)
|
|
89
|
+
|
|
90
|
+
value = opts[:delegated]
|
|
91
|
+
return value if value == true || value == false || value.nil?
|
|
92
|
+
|
|
93
|
+
raise ArgumentError.new(
|
|
94
|
+
"The delegated option must be a boolean; currently have #{value}"
|
|
95
|
+
)
|
|
96
|
+
end
|
|
68
97
|
end
|
|
69
98
|
end
|
|
70
99
|
end
|
|
@@ -22,23 +22,28 @@ module Mongo
|
|
|
22
22
|
#
|
|
23
23
|
# @api private
|
|
24
24
|
class MasterKeyDocument
|
|
25
|
-
# Known KMS provider
|
|
25
|
+
# Known KMS provider types.
|
|
26
26
|
KMS_PROVIDERS = %w[aws azure gcp kmip local].freeze
|
|
27
27
|
|
|
28
|
-
# Creates a master key document object
|
|
28
|
+
# Creates a master key document object from a parameters hash.
|
|
29
29
|
#
|
|
30
|
-
# @param [ String ] kms_provider
|
|
30
|
+
# @param [ String ] kms_provider KMS provider identifier. May be a
|
|
31
|
+
# provider type (e.g. "aws") or a named provider (e.g. "aws:name1").
|
|
31
32
|
# @param [ Hash ] options A hash that contains master key options for
|
|
32
33
|
# the KMS provider.
|
|
33
34
|
# Required parameters for KMS providers are described in corresponding
|
|
34
35
|
# classes inside Mongo::Crypt::KMS module.
|
|
35
36
|
#
|
|
36
|
-
# @raise [ ArgumentError ] If required options are missing or incorrectly
|
|
37
|
+
# @raise [ ArgumentError ] If required options are missing or incorrectly
|
|
38
|
+
# formatted.
|
|
37
39
|
def initialize(kms_provider, options)
|
|
38
40
|
raise ArgumentError.new('Key document options must not be nil') if options.nil?
|
|
39
41
|
|
|
42
|
+
@provider = kms_provider.to_s
|
|
43
|
+
provider_type = KMS.provider_base_type(@provider)
|
|
44
|
+
|
|
40
45
|
master_key = options.fetch(:master_key, {})
|
|
41
|
-
@key_document = case
|
|
46
|
+
@key_document = case provider_type
|
|
42
47
|
when 'aws' then KMS::AWS::MasterKeyDocument.new(master_key)
|
|
43
48
|
when 'azure' then KMS::Azure::MasterKeyDocument.new(master_key)
|
|
44
49
|
when 'gcp' then KMS::GCP::MasterKeyDocument.new(master_key)
|
|
@@ -53,7 +58,7 @@ module Mongo
|
|
|
53
58
|
#
|
|
54
59
|
# @return [ BSON::Document ] Master key document as BSON document.
|
|
55
60
|
def to_document
|
|
56
|
-
@key_document.to_document
|
|
61
|
+
@key_document.to_document.merge(provider: @provider)
|
|
57
62
|
end
|
|
58
63
|
end
|
|
59
64
|
end
|
data/lib/mongo/crypt/kms.rb
CHANGED
|
@@ -16,13 +16,24 @@
|
|
|
16
16
|
|
|
17
17
|
module Mongo
|
|
18
18
|
module Crypt
|
|
19
|
-
module KMS
|
|
19
|
+
module KMS # rubocop:disable Style/Documentation
|
|
20
20
|
# This error indicates that we could not obtain credential for
|
|
21
21
|
# a KMS service.
|
|
22
22
|
#
|
|
23
23
|
# @api private
|
|
24
24
|
class CredentialsNotFound < RuntimeError; end
|
|
25
25
|
|
|
26
|
+
# Returns the base provider type from a KMS provider identifier.
|
|
27
|
+
# For example, "aws:name1" returns "aws" and :aws returns "aws".
|
|
28
|
+
#
|
|
29
|
+
# @param [ String | Symbol ] identifier The KMS provider identifier.
|
|
30
|
+
# @return [ String ] The base provider type.
|
|
31
|
+
#
|
|
32
|
+
# @api private
|
|
33
|
+
def self.provider_base_type(identifier)
|
|
34
|
+
identifier.to_s.split(':').first
|
|
35
|
+
end
|
|
36
|
+
|
|
26
37
|
# This module contains helper methods for validating KMS parameters.
|
|
27
38
|
#
|
|
28
39
|
# @api private
|
data/lib/mongo/cursor.rb
CHANGED
|
@@ -382,7 +382,7 @@ module Mongo
|
|
|
382
382
|
with_overload_retry(context: possibly_refreshed_context) do
|
|
383
383
|
process(execute_operation(get_more_operation))
|
|
384
384
|
end
|
|
385
|
-
rescue Error::SocketError, Error::SocketTimeoutError
|
|
385
|
+
rescue Error::SocketError, Error::SocketTimeoutError, Error::ConnectionPerished
|
|
386
386
|
@get_more_network_error = true
|
|
387
387
|
raise
|
|
388
388
|
rescue Error::OperationFailure => e
|
|
@@ -463,6 +463,12 @@ module Mongo
|
|
|
463
463
|
if view.respond_to?(:options) && view.options.is_a?(Hash) && !view.options[:comment].nil?
|
|
464
464
|
spec[:comment] = view.options[:comment]
|
|
465
465
|
end
|
|
466
|
+
# A cursor built from a runCursorCommand response carries a getMore-specific
|
|
467
|
+
# maxTimeMS that is sent on getMore commands. Regular find/aggregate views
|
|
468
|
+
# do not expose this, so their getMores are unaffected.
|
|
469
|
+
if view.respond_to?(:max_time_ms_for_get_more) && view.max_time_ms_for_get_more
|
|
470
|
+
spec[:max_time_ms] = view.max_time_ms_for_get_more
|
|
471
|
+
end
|
|
466
472
|
Operation::GetMore.new(spec)
|
|
467
473
|
end
|
|
468
474
|
|
|
@@ -501,7 +507,7 @@ module Mongo
|
|
|
501
507
|
end
|
|
502
508
|
|
|
503
509
|
def limit
|
|
504
|
-
@view.
|
|
510
|
+
@view.limit
|
|
505
511
|
end
|
|
506
512
|
|
|
507
513
|
def register
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Copyright (C) 2025 MongoDB Inc.
|
|
4
|
+
#
|
|
5
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
# you may not use this file except in compliance with the License.
|
|
7
|
+
# You may obtain a copy of the License at
|
|
8
|
+
#
|
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
#
|
|
11
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
# See the License for the specific language governing permissions and
|
|
15
|
+
# limitations under the License.
|
|
16
|
+
|
|
17
|
+
module Mongo
|
|
18
|
+
class Database
|
|
19
|
+
# The minimal view a Cursor needs when it is built from an arbitrary
|
|
20
|
+
# command response rather than from a collection query.
|
|
21
|
+
#
|
|
22
|
+
# It carries the getMore-specific options (batchSize, maxTimeMS, comment)
|
|
23
|
+
# and the cursor type and timeout mode, and answers the few methods the
|
|
24
|
+
# Cursor reads from its view.
|
|
25
|
+
#
|
|
26
|
+
# @api private
|
|
27
|
+
class CursorCommandView
|
|
28
|
+
# @param [ Mongo::Database ] database The database the command ran on.
|
|
29
|
+
# @param [ Hash ] options The getMore and timeout options.
|
|
30
|
+
#
|
|
31
|
+
# @option options [ Integer ] :batch_size The batchSize for getMores.
|
|
32
|
+
# @option options [ Integer ] :max_time_ms The maxTimeMS for getMores.
|
|
33
|
+
# @option options [ Object ] :comment The comment for getMores.
|
|
34
|
+
# @option options [ Symbol ] :cursor_type :tailable or :tailable_await.
|
|
35
|
+
# @option options [ Symbol ] :timeout_mode :cursor_lifetime or :iteration.
|
|
36
|
+
def initialize(database, options = {})
|
|
37
|
+
@database = database
|
|
38
|
+
@options = options
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# @return [ Mongo::Database ] The database the command ran on.
|
|
42
|
+
attr_reader :database
|
|
43
|
+
|
|
44
|
+
# @return [ Hash ] The view options. Used by the Cursor to read the
|
|
45
|
+
# getMore comment.
|
|
46
|
+
attr_reader :options
|
|
47
|
+
|
|
48
|
+
# @return [ Mongo::Client ] The client.
|
|
49
|
+
def client
|
|
50
|
+
database.client
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# A placeholder collection used only so the Cursor can reach the client
|
|
54
|
+
# and database. The actual namespace for getMore and killCursors is taken
|
|
55
|
+
# from the command response, not from this collection.
|
|
56
|
+
#
|
|
57
|
+
# @return [ Mongo::Collection ] The $cmd pseudo collection.
|
|
58
|
+
def collection
|
|
59
|
+
@collection ||= Collection.new(database, '$cmd')
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# @return [ Integer | nil ] The batchSize sent on getMore commands.
|
|
63
|
+
def batch_size
|
|
64
|
+
options[:batch_size]
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# @return [ Integer | nil ] The maxTimeMS sent on getMore commands.
|
|
68
|
+
def max_time_ms_for_get_more
|
|
69
|
+
options[:max_time_ms]
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# @return [ Symbol | nil ] The cursor type.
|
|
73
|
+
def cursor_type
|
|
74
|
+
options[:cursor_type]
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# @return [ Symbol | nil ] The timeout mode.
|
|
78
|
+
def timeout_mode
|
|
79
|
+
options[:timeout_mode]
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# @return [ Hash ] timeout values for the operation context.
|
|
83
|
+
def operation_timeouts(opts = {})
|
|
84
|
+
database.operation_timeouts(opts)
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# Cursors do not support a limit when built from a command response.
|
|
88
|
+
#
|
|
89
|
+
# @api private
|
|
90
|
+
def limit
|
|
91
|
+
nil
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
data/lib/mongo/database.rb
CHANGED
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
# limitations under the License.
|
|
16
16
|
|
|
17
17
|
require 'mongo/database/view'
|
|
18
|
+
require 'mongo/database/cursor_command_view'
|
|
18
19
|
|
|
19
20
|
module Mongo
|
|
20
21
|
# Represents a database on the db server and operations that can execute on
|
|
@@ -259,6 +260,119 @@ module Mongo
|
|
|
259
260
|
end
|
|
260
261
|
end
|
|
261
262
|
|
|
263
|
+
# Run a command that returns a cursor and parse the response as a cursor.
|
|
264
|
+
#
|
|
265
|
+
# The command is sent to the server unmodified; the driver MUST NOT inspect
|
|
266
|
+
# or alter it. If the response does not contain a cursor field an error is
|
|
267
|
+
# raised. The command is never retried.
|
|
268
|
+
#
|
|
269
|
+
# Note: if a +maxTimeMS+ field is already set on the command document it is
|
|
270
|
+
# left as-is. The +max_time_ms+ option below applies only to getMore
|
|
271
|
+
# commands. Setting both +timeout_ms+ and +max_time_ms+ is not supported
|
|
272
|
+
# and has undefined behavior.
|
|
273
|
+
#
|
|
274
|
+
# @example Run a cursor-returning command.
|
|
275
|
+
# database.cursor_command(checkMetadataConsistency: 1)
|
|
276
|
+
#
|
|
277
|
+
# @param [ Hash ] command The command to execute.
|
|
278
|
+
# @param [ Hash ] options The command options.
|
|
279
|
+
#
|
|
280
|
+
# @option options [ Hash ] :read The read preference for this command,
|
|
281
|
+
# used for server selection and reused for subsequent getMores.
|
|
282
|
+
# @option options [ Session ] :session The session to use. If none is
|
|
283
|
+
# given an implicit session is created and reused for the cursor's
|
|
284
|
+
# lifetime.
|
|
285
|
+
# @option options [ Integer ] :timeout_ms The operation timeout in
|
|
286
|
+
# milliseconds.
|
|
287
|
+
# @option options [ Integer ] :batch_size The batchSize to send on getMore
|
|
288
|
+
# commands.
|
|
289
|
+
# @option options [ Integer ] :max_time_ms The maxTimeMS to send on getMore
|
|
290
|
+
# commands.
|
|
291
|
+
# @option options [ Object ] :comment A comment to attach to getMore
|
|
292
|
+
# commands.
|
|
293
|
+
# @option options [ Symbol ] :cursor_type The cursor type, :tailable or
|
|
294
|
+
# :tailable_await. Must match the flags set on the command document.
|
|
295
|
+
# @option options [ Symbol ] :timeout_mode :cursor_lifetime or :iteration.
|
|
296
|
+
#
|
|
297
|
+
# @return [ Mongo::Cursor ] A cursor over the command results.
|
|
298
|
+
#
|
|
299
|
+
# @raise [ Error::InvalidCursorOperation ] If the response does not contain
|
|
300
|
+
# a cursor.
|
|
301
|
+
def cursor_command(command, options = {})
|
|
302
|
+
options = options.dup
|
|
303
|
+
execution_opts = options.delete(:execution_options) || {}
|
|
304
|
+
view_options = extract_cursor_command_view_options(options)
|
|
305
|
+
|
|
306
|
+
txn_read_pref = (options[:session].txn_read_preference if options[:session] && options[:session].in_transaction?)
|
|
307
|
+
txn_read_pref ||= options[:read] || ServerSelector::PRIMARY
|
|
308
|
+
Lint.validate_underscore_read_preference(txn_read_pref)
|
|
309
|
+
selector = ServerSelector.get(txn_read_pref)
|
|
310
|
+
|
|
311
|
+
# The session is intentionally not wrapped in #with_session: an implicit
|
|
312
|
+
# session must outlive this method and is ended by the cursor when it is
|
|
313
|
+
# exhausted or closed. Until the cursor takes ownership, the session and
|
|
314
|
+
# any load-balanced connection are cleaned up here on every exit path.
|
|
315
|
+
session = client.get_session(options)
|
|
316
|
+
context = Operation::Context.new(
|
|
317
|
+
client: client,
|
|
318
|
+
session: session,
|
|
319
|
+
operation_timeouts: operation_timeouts(options)
|
|
320
|
+
)
|
|
321
|
+
op = Operation::CursorCommand.new(
|
|
322
|
+
selector: command,
|
|
323
|
+
db_name: name,
|
|
324
|
+
read: selector,
|
|
325
|
+
session: session
|
|
326
|
+
)
|
|
327
|
+
|
|
328
|
+
# Per the client-backpressure spec, retrying a generic command on
|
|
329
|
+
# overload errors requires both retryable reads and writes to be
|
|
330
|
+
# enabled, same as Database#command.
|
|
331
|
+
retry_enabled = client.options[:retry_reads] != false &&
|
|
332
|
+
client.options[:retry_writes] != false
|
|
333
|
+
|
|
334
|
+
server = nil
|
|
335
|
+
connection = nil
|
|
336
|
+
cursor = nil
|
|
337
|
+
begin
|
|
338
|
+
result = with_overload_retry(context: context, retry_enabled: retry_enabled) do
|
|
339
|
+
server = selector.select_server(cluster, nil, session)
|
|
340
|
+
if server.load_balancer?
|
|
341
|
+
# The connection is checked in by the cursor when it is drained.
|
|
342
|
+
connection = check_out_cursor_command_connection(server, context)
|
|
343
|
+
begin
|
|
344
|
+
op.execute_with_connection(connection, context: context, options: execution_opts)
|
|
345
|
+
rescue StandardError
|
|
346
|
+
# Release the connection before the error propagates so that
|
|
347
|
+
# a retried attempt checks out a fresh one.
|
|
348
|
+
connection.connection_pool.check_in(connection) unless connection.pinned?
|
|
349
|
+
connection = nil
|
|
350
|
+
raise
|
|
351
|
+
end
|
|
352
|
+
else
|
|
353
|
+
op.execute(server, context: context, options: execution_opts)
|
|
354
|
+
end
|
|
355
|
+
end
|
|
356
|
+
|
|
357
|
+
unless result.cursor?
|
|
358
|
+
raise Error::InvalidCursorOperation,
|
|
359
|
+
'The command response did not include a cursor. ' \
|
|
360
|
+
'Use Database#command for commands that do not return a cursor.'
|
|
361
|
+
end
|
|
362
|
+
|
|
363
|
+
view = CursorCommandView.new(self, view_options)
|
|
364
|
+
cursor = Cursor.new(view, result, server, session: session, context: context)
|
|
365
|
+
ensure
|
|
366
|
+
# If the cursor was created it owns the session and connection;
|
|
367
|
+
# otherwise (error or no cursor in the response) release them here.
|
|
368
|
+
unless cursor
|
|
369
|
+
connection.connection_pool.check_in(connection) if connection && !connection.pinned?
|
|
370
|
+
session.end_session if session && session.implicit?
|
|
371
|
+
end
|
|
372
|
+
end
|
|
373
|
+
cursor
|
|
374
|
+
end
|
|
375
|
+
|
|
262
376
|
# Execute a read command on the database, retrying the read if necessary.
|
|
263
377
|
#
|
|
264
378
|
# @param [ Hash ] operation The command to execute.
|
|
@@ -530,24 +644,6 @@ module Mongo
|
|
|
530
644
|
)
|
|
531
645
|
end
|
|
532
646
|
|
|
533
|
-
# Create a database for the provided client, for use when we don't want the
|
|
534
|
-
# client's original database instance to be the same.
|
|
535
|
-
#
|
|
536
|
-
# @api private
|
|
537
|
-
#
|
|
538
|
-
# @example Create a database for the client.
|
|
539
|
-
# Database.create(client)
|
|
540
|
-
#
|
|
541
|
-
# @param [ Client ] client The client to create on.
|
|
542
|
-
#
|
|
543
|
-
# @return [ Database ] The database.
|
|
544
|
-
#
|
|
545
|
-
# @since 2.0.0
|
|
546
|
-
def self.create(client)
|
|
547
|
-
database = Database.new(client, client.options[:database], client.options)
|
|
548
|
-
client.instance_variable_set(:@database, database)
|
|
549
|
-
end
|
|
550
|
-
|
|
551
647
|
# @return [ Integer | nil ] Operation timeout that is for this database or
|
|
552
648
|
# for the corresponding client.
|
|
553
649
|
#
|
|
@@ -570,5 +666,35 @@ module Mongo
|
|
|
570
666
|
end
|
|
571
667
|
end
|
|
572
668
|
end
|
|
669
|
+
|
|
670
|
+
private
|
|
671
|
+
|
|
672
|
+
# Removes the getMore and cursor options from the options hash and returns
|
|
673
|
+
# them as a separate hash for the CursorCommandView. The remaining options
|
|
674
|
+
# (e.g. :session, :read, :timeout_ms) are left for command execution.
|
|
675
|
+
#
|
|
676
|
+
# @param [ Hash ] options The cursor_command options (mutated).
|
|
677
|
+
#
|
|
678
|
+
# @return [ Hash ] The view options.
|
|
679
|
+
def extract_cursor_command_view_options(options)
|
|
680
|
+
%i[ batch_size max_time_ms comment cursor_type timeout_mode ].each_with_object({}) do |key, view_options|
|
|
681
|
+
view_options[key] = options.delete(key) if options.key?(key)
|
|
682
|
+
end
|
|
683
|
+
end
|
|
684
|
+
|
|
685
|
+
# Checks out a load balanced connection for a cursor command. If the
|
|
686
|
+
# session is pinned to a connection (e.g. in a transaction), that
|
|
687
|
+
# connection is reused.
|
|
688
|
+
#
|
|
689
|
+
# @param [ Server ] server The load balancer server.
|
|
690
|
+
# @param [ Operation::Context ] context The operation context.
|
|
691
|
+
#
|
|
692
|
+
# @return [ Server::Connection ] The checked out connection.
|
|
693
|
+
def check_out_cursor_command_connection(server, context)
|
|
694
|
+
connection = if context.connection_global_id
|
|
695
|
+
server.pool.check_out_pinned_connection(context.connection_global_id)
|
|
696
|
+
end
|
|
697
|
+
connection || server.pool.check_out(context: context)
|
|
698
|
+
end
|
|
573
699
|
end
|
|
574
700
|
end
|
|
@@ -34,6 +34,11 @@ module Mongo
|
|
|
34
34
|
# @return [ BSON::Document ] result The error result.
|
|
35
35
|
attr_reader :result
|
|
36
36
|
|
|
37
|
+
# @return [ Array<String> ] Deduplicated list of "host:port" addresses of
|
|
38
|
+
# the servers that produced this bulk write error. Empty when no
|
|
39
|
+
# addresses were supplied.
|
|
40
|
+
attr_reader :server_addresses
|
|
41
|
+
|
|
37
42
|
# Instantiate the new exception.
|
|
38
43
|
#
|
|
39
44
|
# @example Instantiate the exception.
|
|
@@ -41,10 +46,14 @@ module Mongo
|
|
|
41
46
|
#
|
|
42
47
|
# @param [ Hash ] result A processed response from the server
|
|
43
48
|
# reporting results of the operation.
|
|
49
|
+
# @param [ Array<String | Mongo::Address | Mongo::Server::Description> ]
|
|
50
|
+
# server_addresses Addresses of the servers that produced this error.
|
|
51
|
+
# Entries are normalized to "host:port" strings.
|
|
44
52
|
#
|
|
45
53
|
# @since 2.0.0
|
|
46
|
-
def initialize(result)
|
|
54
|
+
def initialize(result, server_addresses: nil)
|
|
47
55
|
@result = result
|
|
56
|
+
@server_addresses = normalize_server_addresses(server_addresses)
|
|
48
57
|
|
|
49
58
|
# Exception constructor behaves differently for a nil argument and
|
|
50
59
|
# for no argument. Avoid passing nil explicitly.
|
|
@@ -93,8 +102,27 @@ module Mongo
|
|
|
93
102
|
|
|
94
103
|
fragment = "Multiple errors: #{fragment}" if errors.length > 1
|
|
95
104
|
|
|
105
|
+
if Mongo.include_server_address_in_errors && @server_addresses.any?
|
|
106
|
+
fragment = "#{fragment} (on #{@server_addresses.join(', ')})"
|
|
107
|
+
end
|
|
108
|
+
|
|
96
109
|
fragment
|
|
97
110
|
end
|
|
111
|
+
|
|
112
|
+
def normalize_server_addresses(value)
|
|
113
|
+
return [] if value.nil?
|
|
114
|
+
|
|
115
|
+
Array(value).filter_map do |entry|
|
|
116
|
+
case entry
|
|
117
|
+
when String then entry
|
|
118
|
+
when Mongo::Address then entry.seed
|
|
119
|
+
when Mongo::Server::Description then entry.address&.seed
|
|
120
|
+
else
|
|
121
|
+
raise ArgumentError,
|
|
122
|
+
"server_addresses entries must be String, Mongo::Address, or Mongo::Server::Description; got #{entry.class}"
|
|
123
|
+
end
|
|
124
|
+
end.uniq
|
|
125
|
+
end
|
|
98
126
|
end
|
|
99
127
|
end
|
|
100
128
|
end
|
|
@@ -33,6 +33,9 @@ module Mongo
|
|
|
33
33
|
# Error message when hedge is specified for a read preference that does not support it.
|
|
34
34
|
#
|
|
35
35
|
# @api private
|
|
36
|
+
#
|
|
37
|
+
# @deprecated Hedged reads are deprecated in MongoDB Server 8.0 and will
|
|
38
|
+
# be removed in a future version.
|
|
36
39
|
NO_HEDGE_SUPPORT = 'The hedge option cannot be set for this read preference'
|
|
37
40
|
|
|
38
41
|
# Error message for when the max staleness is not at least twice the heartbeat frequency.
|
|
@@ -23,12 +23,16 @@ module Mongo
|
|
|
23
23
|
class InvalidURI < Error
|
|
24
24
|
# Instantiate the new exception.
|
|
25
25
|
#
|
|
26
|
+
# The URI is redacted via {Mongo::URI.redact} before being interpolated
|
|
27
|
+
# so that any cleartext credentials in the original input do not end up
|
|
28
|
+
# in logs, error reporters, or backtraces.
|
|
29
|
+
#
|
|
26
30
|
# @example Instantiate the exception.
|
|
27
31
|
# Mongo::Error::InvalidURI.new(uri, details, format)
|
|
28
32
|
#
|
|
29
33
|
# @since 2.0.0
|
|
30
34
|
def initialize(uri, details, format = nil)
|
|
31
|
-
message = "Bad URI: #{uri}\n" +
|
|
35
|
+
message = "Bad URI: #{Mongo::URI.redact(uri)}\n" +
|
|
32
36
|
"#{details}\n"
|
|
33
37
|
message += "MongoDB URI must be in the following format: #{format}\n" if format
|
|
34
38
|
message += "Please see the following URL for more information: #{Mongo::URI::HELP}\n"
|