mongo 2.19.3 → 2.20.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
- checksums.yaml.gz.sig +0 -0
- data/Rakefile +27 -154
- data/lib/mongo/cluster/topology/base.rb +16 -0
- data/lib/mongo/cluster.rb +27 -1
- data/lib/mongo/collection/view/iterable.rb +1 -0
- data/lib/mongo/collection.rb +4 -2
- data/lib/mongo/error/transactions_not_supported.rb +34 -0
- data/lib/mongo/error.rb +1 -0
- data/lib/mongo/grid/fs_bucket.rb +6 -0
- data/lib/mongo/monitoring/event/secure.rb +1 -1
- data/lib/mongo/operation/shared/executable.rb +43 -27
- data/lib/mongo/operation/shared/response_handling.rb +23 -25
- data/lib/mongo/retryable/read_worker.rb +7 -6
- data/lib/mongo/retryable/write_worker.rb +7 -4
- data/lib/mongo/retryable.rb +2 -2
- data/lib/mongo/server/app_metadata/environment.rb +64 -9
- data/lib/mongo/server/app_metadata.rb +5 -4
- data/lib/mongo/server/description/features.rb +1 -0
- data/lib/mongo/server_selector/base.rb +32 -6
- data/lib/mongo/session/server_session/dirtyable.rb +52 -0
- data/lib/mongo/session/server_session.rb +3 -0
- data/lib/mongo/session/session_pool.rb +12 -18
- data/lib/mongo/session.rb +32 -0
- data/lib/mongo/uri.rb +0 -4
- data/lib/mongo/version.rb +1 -1
- data/mongo.gemspec +1 -7
- data/spec/atlas/atlas_connectivity_spec.rb +4 -4
- data/spec/faas/ruby-sam-app/Gemfile +9 -0
- data/spec/faas/ruby-sam-app/mongodb/Gemfile +4 -0
- data/spec/faas/ruby-sam-app/mongodb/app.rb +149 -0
- data/spec/faas/ruby-sam-app/template.yaml +48 -0
- data/spec/integration/client_side_encryption/corpus_spec.rb +10 -2
- data/spec/integration/retryable_reads_errors_spec.rb +161 -8
- data/spec/integration/retryable_writes_errors_spec.rb +156 -0
- data/spec/mongo/cluster_spec.rb +36 -0
- data/spec/mongo/collection/view/aggregation_spec.rb +6 -1
- data/spec/mongo/collection/view/explainable_spec.rb +2 -0
- data/spec/mongo/collection_crud_spec.rb +1 -1
- data/spec/mongo/operation/insert_spec.rb +1 -1
- data/spec/mongo/retryable/write_worker_spec.rb +39 -0
- data/spec/mongo/server/app_metadata/environment_spec.rb +135 -0
- data/spec/mongo/server/app_metadata_spec.rb +12 -2
- data/spec/mongo/server/connection_spec.rb +4 -0
- data/spec/mongo/session/session_pool_spec.rb +1 -16
- data/spec/mongo/session_transaction_spec.rb +15 -0
- data/spec/mongo/uri_spec.rb +0 -9
- data/spec/runners/crud/test.rb +0 -8
- data/spec/runners/crud.rb +1 -1
- data/spec/runners/transactions/test.rb +12 -3
- data/spec/runners/unified/assertions.rb +16 -3
- data/spec/runners/unified/crud_operations.rb +12 -0
- data/spec/runners/unified/support_operations.rb +3 -5
- data/spec/runners/unified/test.rb +8 -1
- data/spec/shared/lib/mrss/docker_runner.rb +3 -0
- data/spec/shared/share/Dockerfile.erb +20 -69
- data/spec/shared/shlib/server.sh +1 -0
- data/spec/shared/shlib/set_env.sh +5 -28
- data/spec/spec_tests/data/client_side_encryption/explain.yml +2 -2
- data/spec/spec_tests/data/connection_string/invalid-uris.yml +0 -10
- data/spec/spec_tests/data/connection_string/valid-options.yml +13 -0
- data/spec/spec_tests/data/crud_unified/find-test-all-options.yml +348 -0
- data/spec/spec_tests/data/index_management/createSearchIndex.yml +5 -3
- data/spec/spec_tests/data/index_management/createSearchIndexes.yml +7 -4
- data/spec/spec_tests/data/index_management/dropSearchIndex.yml +2 -1
- data/spec/spec_tests/data/index_management/listSearchIndexes.yml +13 -7
- data/spec/spec_tests/data/index_management/updateSearchIndex.yml +2 -1
- data/spec/spec_tests/data/retryable_writes/unified/bulkWrite-serverErrors.yml +3 -6
- data/spec/spec_tests/data/retryable_writes/unified/insertOne-serverErrors.yml +3 -6
- data/spec/spec_tests/data/run_command_unified/runCommand.yml +319 -0
- data/spec/spec_tests/data/sessions_unified/driver-sessions-dirty-session-errors.yml +351 -0
- data/spec/spec_tests/data/unified/valid-pass/poc-crud.yml +1 -1
- data/spec/spec_tests/data/unified/valid-pass/poc-retryable-writes.yml +7 -7
- data/spec/spec_tests/data/unified/valid-pass/poc-sessions.yml +3 -4
- data/spec/spec_tests/data/unified/valid-pass/poc-transactions-convenient-api.yml +1 -1
- data/spec/spec_tests/data/unified/valid-pass/poc-transactions-mongos-pin-auto.yml +1 -1
- data/spec/spec_tests/data/unified/valid-pass/poc-transactions.yml +3 -3
- data/spec/spec_tests/run_command_unified_spec.rb +13 -0
- data/spec/spec_tests/sdam_unified_spec.rb +2 -0
- data/spec/support/constraints.rb +6 -0
- data/spec/support/ocsp +1 -1
- data/spec/support/recording_logger.rb +27 -0
- data.tar.gz.sig +0 -0
- metadata +1272 -1253
- metadata.gz.sig +0 -0
- data/spec/spec_tests/data/cmap/pool-clear-interrupt-immediately.yml +0 -49
@@ -18,9 +18,12 @@ module Mongo
|
|
18
18
|
class Server
|
19
19
|
class AppMetadata
|
20
20
|
# Implements the logic from the handshake spec, for deducing and
|
21
|
-
# reporting the current
|
21
|
+
# reporting the current environment in which the program is
|
22
22
|
# executing.
|
23
23
|
#
|
24
|
+
# This includes FaaS environment checks, as well as checks for the
|
25
|
+
# presence of a container (Docker) and/or orchestrator (Kubernetes).
|
26
|
+
#
|
24
27
|
# @api private
|
25
28
|
class Environment
|
26
29
|
# Error class for reporting that too many discriminators were found
|
@@ -39,6 +42,10 @@ module Mongo
|
|
39
42
|
# Error class for reporting that the value for a field is too long.
|
40
43
|
class ValueTooLong < Mongo::Error; end
|
41
44
|
|
45
|
+
# The name and location of the .dockerenv file that will signal the
|
46
|
+
# presence of Docker.
|
47
|
+
DOCKERENV_PATH = '/.dockerenv'
|
48
|
+
|
42
49
|
# This value is not explicitly specified in the spec, only implied to be
|
43
50
|
# less than 512.
|
44
51
|
MAXIMUM_VALUE_LENGTH = 500
|
@@ -102,9 +109,11 @@ module Mongo
|
|
102
109
|
# if the environment contains invalid or contradictory state, it will
|
103
110
|
# be initialized with {{name}} set to {{nil}}.
|
104
111
|
def initialize
|
112
|
+
@fields = {}
|
105
113
|
@error = nil
|
106
114
|
@name = detect_environment
|
107
|
-
|
115
|
+
populate_faas_fields
|
116
|
+
detect_container
|
108
117
|
rescue TooManyEnvironments => e
|
109
118
|
self.error = "too many environments detected: #{e.message}"
|
110
119
|
rescue MissingVariable => e
|
@@ -115,6 +124,23 @@ module Mongo
|
|
115
124
|
self.error = "value for #{e.message} is too long"
|
116
125
|
end
|
117
126
|
|
127
|
+
# Queries the detected container information.
|
128
|
+
#
|
129
|
+
# @return [ Hash | nil ] the detected container information, or
|
130
|
+
# nil if no container was detected.
|
131
|
+
def container
|
132
|
+
fields[:container]
|
133
|
+
end
|
134
|
+
|
135
|
+
# Queries whether any environment information was able to be
|
136
|
+
# detected.
|
137
|
+
#
|
138
|
+
# @return [ true | false ] if any environment information was
|
139
|
+
# detected.
|
140
|
+
def present?
|
141
|
+
@name || fields.any?
|
142
|
+
end
|
143
|
+
|
118
144
|
# Queries whether the current environment is a valid FaaS environment.
|
119
145
|
#
|
120
146
|
# @return [ true | false ] whether the environment is a FaaS
|
@@ -159,14 +185,11 @@ module Mongo
|
|
159
185
|
@name == 'vercel'
|
160
186
|
end
|
161
187
|
|
162
|
-
# Compiles the detected environment information into a Hash.
|
163
|
-
# always include a {{name}} key, but may include other keys as well,
|
164
|
-
# depending on the detected FaaS environment. (See the handshake
|
165
|
-
# spec for details.)
|
188
|
+
# Compiles the detected environment information into a Hash.
|
166
189
|
#
|
167
190
|
# @return [ Hash ] the detected environment information.
|
168
191
|
def to_h
|
169
|
-
fields.merge(name: name)
|
192
|
+
name ? fields.merge(name: name) : fields
|
170
193
|
end
|
171
194
|
|
172
195
|
private
|
@@ -192,6 +215,38 @@ module Mongo
|
|
192
215
|
names.first
|
193
216
|
end
|
194
217
|
|
218
|
+
# Looks for the presence of a container. Currently can detect
|
219
|
+
# Docker (by the existence of a .dockerenv file in the root
|
220
|
+
# directory) and Kubernetes (by the existence of the KUBERNETES_SERVICE_HOST
|
221
|
+
# environment variable).
|
222
|
+
def detect_container
|
223
|
+
runtime = docker_present? && 'docker'
|
224
|
+
orchestrator = kubernetes_present? && 'kubernetes'
|
225
|
+
|
226
|
+
return unless runtime || orchestrator
|
227
|
+
|
228
|
+
fields[:container] = {}
|
229
|
+
fields[:container][:runtime] = runtime if runtime
|
230
|
+
fields[:container][:orchestrator] = orchestrator if orchestrator
|
231
|
+
end
|
232
|
+
|
233
|
+
# Checks for the existence of a .dockerenv in the root directory.
|
234
|
+
def docker_present?
|
235
|
+
File.exist?(dockerenv_path)
|
236
|
+
end
|
237
|
+
|
238
|
+
# Implementing this as a method so that it can be mocked in tests, to
|
239
|
+
# test the presence or absence of Docker.
|
240
|
+
def dockerenv_path
|
241
|
+
DOCKERENV_PATH
|
242
|
+
end
|
243
|
+
|
244
|
+
# Checks for the presence of a non-empty KUBERNETES_SERVICE_HOST
|
245
|
+
# environment variable.
|
246
|
+
def kubernetes_present?
|
247
|
+
!ENV['KUBERNETES_SERVICE_HOST'].to_s.empty?
|
248
|
+
end
|
249
|
+
|
195
250
|
# Determines whether the named environment variable exists, and (if
|
196
251
|
# a pattern has been declared for that descriminator) whether the
|
197
252
|
# pattern matches the value of the variable.
|
@@ -212,10 +267,10 @@ module Mongo
|
|
212
267
|
# Extracts environment information from the current environment
|
213
268
|
# variables, based on the detected FaaS environment. Populates the
|
214
269
|
# {{@fields}} instance variable.
|
215
|
-
def
|
270
|
+
def populate_faas_fields
|
216
271
|
return unless name
|
217
272
|
|
218
|
-
|
273
|
+
FIELDS[name].each_with_object(@fields) do |(var, defn), fields|
|
219
274
|
fields[defn[:field]] = extract_field(var, defn)
|
220
275
|
end
|
221
276
|
end
|
@@ -187,13 +187,14 @@ module Mongo
|
|
187
187
|
}
|
188
188
|
end
|
189
189
|
|
190
|
-
# Returns the environment doc describing the current
|
190
|
+
# Returns the environment doc describing the current execution
|
191
|
+
# environment.
|
191
192
|
#
|
192
|
-
# @return [ Hash | nil ] the environment doc (or nil if
|
193
|
-
#
|
193
|
+
# @return [ Hash | nil ] the environment doc (or nil if no relevant
|
194
|
+
# environment info was detected)
|
194
195
|
def env_doc
|
195
196
|
env = Environment.new
|
196
|
-
env.
|
197
|
+
env.present? ? env.to_h : nil
|
197
198
|
end
|
198
199
|
|
199
200
|
def type
|
@@ -48,6 +48,7 @@ module Mongo
|
|
48
48
|
# provided by the client during findAndModify operations, requiring the
|
49
49
|
# driver to raise client-side errors when those options are provided.
|
50
50
|
find_and_modify_option_validation: 8,
|
51
|
+
sharded_transactions: 8,
|
51
52
|
transactions: 7,
|
52
53
|
scram_sha_256: 7,
|
53
54
|
array_filters: 6,
|
@@ -164,6 +164,10 @@ module Mongo
|
|
164
164
|
# for mongos pinning. Added in version 2.10.0.
|
165
165
|
# @param [ true | false ] write_aggregation Whether we need a server that
|
166
166
|
# supports writing aggregations (e.g. with $merge/$out) on secondaries.
|
167
|
+
# @param [ Array<Server> ] deprioritized A list of servers that should
|
168
|
+
# be selected from only if no other servers are available. This is
|
169
|
+
# used to avoid selecting the same server twice in a row when
|
170
|
+
# retrying a command.
|
167
171
|
#
|
168
172
|
# @return [ Mongo::Server ] A server matching the server preference.
|
169
173
|
#
|
@@ -174,8 +178,8 @@ module Mongo
|
|
174
178
|
# lint mode is enabled.
|
175
179
|
#
|
176
180
|
# @since 2.0.0
|
177
|
-
def select_server(cluster, ping = nil, session = nil, write_aggregation: false)
|
178
|
-
select_server_impl(cluster, ping, session, write_aggregation).tap do |server|
|
181
|
+
def select_server(cluster, ping = nil, session = nil, write_aggregation: false, deprioritized: [])
|
182
|
+
select_server_impl(cluster, ping, session, write_aggregation, deprioritized).tap do |server|
|
179
183
|
if Lint.enabled? && !server.pool.ready?
|
180
184
|
raise Error::LintError, 'Server selector returning a server with a pool which is not ready'
|
181
185
|
end
|
@@ -183,7 +187,7 @@ module Mongo
|
|
183
187
|
end
|
184
188
|
|
185
189
|
# Parameters and return values are the same as for select_server.
|
186
|
-
private def select_server_impl(cluster, ping, session, write_aggregation)
|
190
|
+
private def select_server_impl(cluster, ping, session, write_aggregation, deprioritized)
|
187
191
|
if cluster.topology.is_a?(Cluster::Topology::LoadBalanced)
|
188
192
|
return cluster.servers.first
|
189
193
|
end
|
@@ -266,7 +270,7 @@ module Mongo
|
|
266
270
|
end
|
267
271
|
end
|
268
272
|
|
269
|
-
server = try_select_server(cluster, write_aggregation: write_aggregation)
|
273
|
+
server = try_select_server(cluster, write_aggregation: write_aggregation, deprioritized: deprioritized)
|
270
274
|
|
271
275
|
if server
|
272
276
|
unless cluster.topology.compatible?
|
@@ -321,11 +325,15 @@ module Mongo
|
|
321
325
|
# an eligible server.
|
322
326
|
# @param [ true | false ] write_aggregation Whether we need a server that
|
323
327
|
# supports writing aggregations (e.g. with $merge/$out) on secondaries.
|
328
|
+
# @param [ Array<Server> ] deprioritized A list of servers that should
|
329
|
+
# be selected from only if no other servers are available. This is
|
330
|
+
# used to avoid selecting the same server twice in a row when
|
331
|
+
# retrying a command.
|
324
332
|
#
|
325
333
|
# @return [ Server | nil ] A suitable server, if one exists.
|
326
334
|
#
|
327
335
|
# @api private
|
328
|
-
def try_select_server(cluster, write_aggregation: false)
|
336
|
+
def try_select_server(cluster, write_aggregation: false, deprioritized: [])
|
329
337
|
servers = if write_aggregation && cluster.replica_set?
|
330
338
|
# 1. Check if ALL servers in cluster support secondary writes.
|
331
339
|
is_write_supported = cluster.servers.reduce(true) do |res, server|
|
@@ -347,7 +355,7 @@ module Mongo
|
|
347
355
|
# by the selector (e.g. for secondary preferred, the first
|
348
356
|
# server may be a secondary and the second server may be primary)
|
349
357
|
# and we should take the first server here respecting the order
|
350
|
-
server = servers
|
358
|
+
server = suitable_server(servers, deprioritized)
|
351
359
|
|
352
360
|
if server
|
353
361
|
if Lint.enabled?
|
@@ -418,6 +426,24 @@ module Mongo
|
|
418
426
|
|
419
427
|
private
|
420
428
|
|
429
|
+
# Returns a server from the list of servers that is suitable for
|
430
|
+
# executing the operation.
|
431
|
+
#
|
432
|
+
# @param [ Array<Server> ] servers The candidate servers.
|
433
|
+
# @param [ Array<Server> ] deprioritized A list of servers that should
|
434
|
+
# be selected from only if no other servers are available.
|
435
|
+
#
|
436
|
+
# @return [ Server | nil ] The suitable server or nil if no suitable
|
437
|
+
# server is available.
|
438
|
+
def suitable_server(servers, deprioritized)
|
439
|
+
preferred = servers - deprioritized
|
440
|
+
if preferred.empty?
|
441
|
+
servers.first
|
442
|
+
else
|
443
|
+
preferred.first
|
444
|
+
end
|
445
|
+
end
|
446
|
+
|
421
447
|
# Convert this server preference definition into a format appropriate
|
422
448
|
# for sending to a MongoDB server (i.e., as a command field).
|
423
449
|
#
|
@@ -0,0 +1,52 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Copyright (C) 2024 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 Session
|
19
|
+
class ServerSession
|
20
|
+
# Functionality for manipulating and querying a session's
|
21
|
+
# "dirty" state, per the last paragraph at
|
22
|
+
# https://github.com/mongodb/specifications/blob/master/source/sessions/driver-sessions.rst#server-session-pool
|
23
|
+
#
|
24
|
+
# If a driver has a server session pool and a network error is
|
25
|
+
# encountered when executing any command with a ClientSession, the
|
26
|
+
# driver MUST mark the associated ServerSession as dirty. Dirty server
|
27
|
+
# sessions are discarded when returned to the server session pool. It is
|
28
|
+
# valid for a dirty session to be used for subsequent commands (e.g. an
|
29
|
+
# implicit retry attempt, a later command in a bulk write, or a later
|
30
|
+
# operation on an explicit session), however, it MUST remain dirty for
|
31
|
+
# the remainder of its lifetime regardless if later commands succeed.
|
32
|
+
#
|
33
|
+
# @api private
|
34
|
+
module Dirtyable
|
35
|
+
# Query whether the server session has been marked dirty or not.
|
36
|
+
#
|
37
|
+
# @return [ true | false ] the server session's dirty state
|
38
|
+
def dirty?
|
39
|
+
@dirty
|
40
|
+
end
|
41
|
+
|
42
|
+
# Mark the server session as dirty (the default) or clean.
|
43
|
+
#
|
44
|
+
# @param [ true | false ] mark whether the mark the server session
|
45
|
+
# dirty or not.
|
46
|
+
def dirty!(mark = true)
|
47
|
+
@dirty = mark
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -15,6 +15,8 @@
|
|
15
15
|
# See the License for the specific language governing permissions and
|
16
16
|
# limitations under the License.
|
17
17
|
|
18
|
+
require 'mongo/session/server_session/dirtyable'
|
19
|
+
|
18
20
|
module Mongo
|
19
21
|
|
20
22
|
class Session
|
@@ -25,6 +27,7 @@ module Mongo
|
|
25
27
|
#
|
26
28
|
# @since 2.5.0
|
27
29
|
class ServerSession
|
30
|
+
include Dirtyable
|
28
31
|
|
29
32
|
# Regex for removing dashes from the UUID string.
|
30
33
|
#
|
@@ -25,21 +25,6 @@ module Mongo
|
|
25
25
|
#
|
26
26
|
# @since 2.5.0
|
27
27
|
class SessionPool
|
28
|
-
|
29
|
-
# Create a SessionPool.
|
30
|
-
#
|
31
|
-
# @example
|
32
|
-
# SessionPool.create(cluster)
|
33
|
-
#
|
34
|
-
# @param [ Mongo::Cluster ] cluster The cluster that will be associated with this
|
35
|
-
# session pool.
|
36
|
-
#
|
37
|
-
# @since 2.5.0
|
38
|
-
def self.create(cluster)
|
39
|
-
pool = new(cluster)
|
40
|
-
cluster.instance_variable_set(:@session_pool, pool)
|
41
|
-
end
|
42
|
-
|
43
28
|
# Initialize a SessionPool.
|
44
29
|
#
|
45
30
|
# @example
|
@@ -105,9 +90,7 @@ module Mongo
|
|
105
90
|
|
106
91
|
@mutex.synchronize do
|
107
92
|
prune!
|
108
|
-
|
109
|
-
@queue.unshift(session)
|
110
|
-
end
|
93
|
+
@queue.unshift(session) if return_to_queue?(session)
|
111
94
|
end
|
112
95
|
end
|
113
96
|
|
@@ -136,6 +119,17 @@ module Mongo
|
|
136
119
|
|
137
120
|
private
|
138
121
|
|
122
|
+
# Query whether the given session is okay to return to the
|
123
|
+
# pool's queue.
|
124
|
+
#
|
125
|
+
# @param [ Session::ServerSession ] session the session to query
|
126
|
+
#
|
127
|
+
# @return [ true | false ] whether to return the session to the
|
128
|
+
# queue.
|
129
|
+
def return_to_queue?(session)
|
130
|
+
!session.dirty? && !about_to_expire?(session)
|
131
|
+
end
|
132
|
+
|
139
133
|
def about_to_expire?(session)
|
140
134
|
if session.nil?
|
141
135
|
raise ArgumentError, 'session cannot be nil'
|
data/lib/mongo/session.rb
CHANGED
@@ -123,6 +123,23 @@ module Mongo
|
|
123
123
|
# @since 2.5.0
|
124
124
|
attr_reader :operation_time
|
125
125
|
|
126
|
+
# Sets the dirty state to the given value for the underlying server
|
127
|
+
# session. If there is no server session, this does nothing.
|
128
|
+
#
|
129
|
+
# @param [ true | false ] mark whether to mark the server session as
|
130
|
+
# dirty, or not.
|
131
|
+
def dirty!(mark = true)
|
132
|
+
@server_session&.dirty!(mark)
|
133
|
+
end
|
134
|
+
|
135
|
+
# @return [ true | false | nil ] whether the underlying server session is
|
136
|
+
# dirty. If no server session exists for this session, returns nil.
|
137
|
+
#
|
138
|
+
# @api private
|
139
|
+
def dirty?
|
140
|
+
@server_session&.dirty?
|
141
|
+
end
|
142
|
+
|
126
143
|
# @return [ Hash ] The options for the transaction currently being executed
|
127
144
|
# on this session.
|
128
145
|
#
|
@@ -538,6 +555,8 @@ module Mongo
|
|
538
555
|
#
|
539
556
|
# @since 2.6.0
|
540
557
|
def start_transaction(options = nil)
|
558
|
+
check_transactions_supported!
|
559
|
+
|
541
560
|
if options
|
542
561
|
Lint.validate_read_concern_option(options[:read_concern])
|
543
562
|
|
@@ -1185,5 +1204,18 @@ module Mongo
|
|
1185
1204
|
raise Mongo::Error::InvalidSession.new(MISMATCHED_CLUSTER_ERROR_MSG)
|
1186
1205
|
end
|
1187
1206
|
end
|
1207
|
+
|
1208
|
+
def check_transactions_supported!
|
1209
|
+
raise Mongo::Error::TransactionsNotSupported, "standalone topology" if cluster.single?
|
1210
|
+
|
1211
|
+
cluster.next_primary.with_connection do |conn|
|
1212
|
+
if cluster.replica_set? && !conn.features.transactions_enabled?
|
1213
|
+
raise Mongo::Error::TransactionsNotSupported, "server version is < 4.0"
|
1214
|
+
end
|
1215
|
+
if cluster.sharded? && !conn.features.sharded_transactions_enabled?
|
1216
|
+
raise Mongo::Error::TransactionsNotSupported, "sharded transactions require server version >= 4.2"
|
1217
|
+
end
|
1218
|
+
end
|
1219
|
+
end
|
1188
1220
|
end
|
1189
1221
|
end
|
data/lib/mongo/uri.rb
CHANGED
@@ -377,10 +377,6 @@ module Mongo
|
|
377
377
|
raise_invalid_error!("Options contain an unescaped question mark (?), or the database name contains a question mark and was not escaped")
|
378
378
|
end
|
379
379
|
|
380
|
-
if options && !hosts_and_db.index('/')
|
381
|
-
raise_invalid_error!("MongoDB URI must have a slash (/) after the hosts if options are given")
|
382
|
-
end
|
383
|
-
|
384
380
|
hosts, db = hosts_and_db.split('/', 2)
|
385
381
|
if db && db.index('/')
|
386
382
|
raise_invalid_error!("Database name contains an unescaped slash (/): #{db}")
|
data/lib/mongo/version.rb
CHANGED
data/mongo.gemspec
CHANGED
@@ -41,11 +41,5 @@ Gem::Specification.new do |s|
|
|
41
41
|
|
42
42
|
s.required_ruby_version = ">= 2.5"
|
43
43
|
|
44
|
-
|
45
|
-
# but in release version we want to depend on bson < 5.0.0.
|
46
|
-
if %w(1 yes true).include?(ENV['MONGO_RUBY_DRIVER_BSON_MASTER'])
|
47
|
-
s.add_dependency 'bson', '>=4.13.0', '<6.0.0'
|
48
|
-
else
|
49
|
-
s.add_dependency 'bson', '>=4.14.1', '<5.0.0'
|
50
|
-
end
|
44
|
+
s.add_dependency 'bson', '>=4.14.1', '<6.0.0'
|
51
45
|
end
|
@@ -11,13 +11,13 @@ describe 'Atlas connectivity' do
|
|
11
11
|
|
12
12
|
describe 'connection to Atlas' do
|
13
13
|
it 'runs ismaster successfully' do
|
14
|
-
|
15
|
-
|
14
|
+
expect { client.database.command(:hello => 1) }
|
15
|
+
.not_to raise_error
|
16
16
|
end
|
17
17
|
|
18
18
|
it 'runs findOne successfully' do
|
19
|
-
|
20
|
-
|
19
|
+
expect { client.use(:test)['test'].find.to_a }
|
20
|
+
.not_to raise_error
|
21
21
|
end
|
22
22
|
end
|
23
23
|
end
|
@@ -0,0 +1,149 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'mongo'
|
4
|
+
require 'json'
|
5
|
+
|
6
|
+
class StatsAggregator
|
7
|
+
|
8
|
+
def initialize
|
9
|
+
@open_connections = 0
|
10
|
+
@heartbeats_count = 0
|
11
|
+
@total_heartbeat_time = 0
|
12
|
+
@commands_count = 0
|
13
|
+
@total_command_time = 0
|
14
|
+
end
|
15
|
+
|
16
|
+
def add_command(duration)
|
17
|
+
@commands_count += 1
|
18
|
+
@total_command_time += duration
|
19
|
+
end
|
20
|
+
|
21
|
+
def add_heartbeat(duration)
|
22
|
+
@heartbeats_count += 1
|
23
|
+
@total_heartbeat_time += duration
|
24
|
+
end
|
25
|
+
|
26
|
+
def add_connection
|
27
|
+
@open_connections += 1
|
28
|
+
end
|
29
|
+
|
30
|
+
def remove_connection
|
31
|
+
@open_connections -= 1
|
32
|
+
end
|
33
|
+
|
34
|
+
def average_heartbeat_time
|
35
|
+
if @heartbeats_count == 0
|
36
|
+
0
|
37
|
+
else
|
38
|
+
@total_heartbeat_time / @heartbeats_count
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def average_command_time
|
43
|
+
if @commands_count == 0
|
44
|
+
0
|
45
|
+
else
|
46
|
+
@total_command_time / @commands_count
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def reset
|
51
|
+
@open_connections = 0
|
52
|
+
@heartbeats_count = 0
|
53
|
+
@total_heartbeat_time = 0
|
54
|
+
@commands_count = 0
|
55
|
+
@total_command_time = 0
|
56
|
+
end
|
57
|
+
|
58
|
+
def result
|
59
|
+
{
|
60
|
+
average_heartbeat_time: average_heartbeat_time,
|
61
|
+
average_command_time: average_command_time,
|
62
|
+
heartbeats_count: @heartbeats_count,
|
63
|
+
open_connections: @open_connections,
|
64
|
+
}
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
class CommandMonitor
|
69
|
+
|
70
|
+
def initialize(stats_aggregator)
|
71
|
+
@stats_aggregator = stats_aggregator
|
72
|
+
end
|
73
|
+
|
74
|
+
def started(event); end
|
75
|
+
|
76
|
+
def failed(event)
|
77
|
+
@stats_aggregator.add_command(event.duration)
|
78
|
+
end
|
79
|
+
|
80
|
+
def succeeded(event)
|
81
|
+
@stats_aggregator.add_command(event.duration)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
class HeartbeatMonitor
|
86
|
+
|
87
|
+
def initialize(stats_aggregator)
|
88
|
+
@stats_aggregator = stats_aggregator
|
89
|
+
end
|
90
|
+
|
91
|
+
def started(event); end
|
92
|
+
|
93
|
+
def succeeded(event)
|
94
|
+
@stats_aggregator.add_heartbeat(event.duration)
|
95
|
+
end
|
96
|
+
|
97
|
+
def failed(event)
|
98
|
+
@stats_aggregator.add_heartbeat(event.duration)
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
class PoolMonitor
|
103
|
+
|
104
|
+
def initialize(stats_aggregator)
|
105
|
+
@stats_aggregator = stats_aggregator
|
106
|
+
end
|
107
|
+
|
108
|
+
def published(event)
|
109
|
+
case event
|
110
|
+
when Mongo::Monitoring::Event::Cmap::ConnectionCreated
|
111
|
+
@stats_aggregator.add_connection
|
112
|
+
when Mongo::Monitoring::Event::Cmap::ConnectionClosed
|
113
|
+
@stats_aggregator.remove_connection
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
$stats_aggregator = StatsAggregator.new
|
119
|
+
|
120
|
+
command_monitor = CommandMonitor.new($stats_aggregator)
|
121
|
+
heartbeat_monitor = HeartbeatMonitor.new($stats_aggregator)
|
122
|
+
pool_monitor = PoolMonitor.new($stats_aggregator)
|
123
|
+
|
124
|
+
sdam_proc = proc do |client|
|
125
|
+
client.subscribe(Mongo::Monitoring::COMMAND, command_monitor)
|
126
|
+
client.subscribe(Mongo::Monitoring::SERVER_HEARTBEAT, heartbeat_monitor)
|
127
|
+
client.subscribe(Mongo::Monitoring::CONNECTION_POOL, pool_monitor)
|
128
|
+
end
|
129
|
+
|
130
|
+
puts 'Connecting'
|
131
|
+
$client = Mongo::Client.new(ENV['MONGODB_URI'], sdam_proc: sdam_proc)
|
132
|
+
# Populate the connection pool
|
133
|
+
$client.use('lambda_test').database.list_collections
|
134
|
+
puts 'Connected'
|
135
|
+
|
136
|
+
def lambda_handler(event:, context:)
|
137
|
+
db = $client.use('lambda_test')
|
138
|
+
collection = db[:test_collection]
|
139
|
+
result = collection.insert_one({ name: 'test' })
|
140
|
+
collection.delete_one({ _id: result.inserted_id })
|
141
|
+
response = $stats_aggregator.result.to_json
|
142
|
+
$stats_aggregator.reset
|
143
|
+
puts "Response: #{response}"
|
144
|
+
|
145
|
+
{
|
146
|
+
statusCode: 200,
|
147
|
+
body: response
|
148
|
+
}
|
149
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
AWSTemplateFormatVersion: '2010-09-09'
|
2
|
+
Transform: AWS::Serverless-2016-10-31
|
3
|
+
Description: >
|
4
|
+
Sample SAM Template for ruby-sam-app
|
5
|
+
|
6
|
+
# More info about Globals: https://github.com/awslabs/serverless-application-model/blob/master/docs/globals.rst
|
7
|
+
Globals:
|
8
|
+
Function:
|
9
|
+
Timeout: 30
|
10
|
+
MemorySize: 128
|
11
|
+
|
12
|
+
Parameters:
|
13
|
+
MongoDbUri:
|
14
|
+
Type: String
|
15
|
+
Description: The MongoDB connection string.
|
16
|
+
|
17
|
+
Resources:
|
18
|
+
MongoDBFunction:
|
19
|
+
Type: AWS::Serverless::Function # More info about Function Resource: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction
|
20
|
+
Properties:
|
21
|
+
CodeUri: mongodb/
|
22
|
+
Environment:
|
23
|
+
Variables:
|
24
|
+
MONGODB_URI: !Ref MongoDbUri
|
25
|
+
Handler: app.lambda_handler
|
26
|
+
Runtime: ruby3.2
|
27
|
+
Architectures:
|
28
|
+
- x86_64
|
29
|
+
Events:
|
30
|
+
MongoDB:
|
31
|
+
Type: Api # More info about API Event Source: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#api
|
32
|
+
Properties:
|
33
|
+
Path: /mongodb
|
34
|
+
Method: get
|
35
|
+
|
36
|
+
Outputs:
|
37
|
+
# ServerlessRestApi is an implicit API created out of Events key under Serverless::Function
|
38
|
+
# Find out more about other implicit resources you can reference within SAM
|
39
|
+
# https://github.com/awslabs/serverless-application-model/blob/master/docs/internals/generated_resources.rst#api
|
40
|
+
MongoDBApi:
|
41
|
+
Description: "API Gateway endpoint URL for Prod stage for MongoDB function"
|
42
|
+
Value: !Sub "https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/mongodb/"
|
43
|
+
MongoDBFunction:
|
44
|
+
Description: "MongoDB Lambda Function ARN"
|
45
|
+
Value: !GetAtt MongoDBFunction.Arn
|
46
|
+
MongoDBFunctionIamRole:
|
47
|
+
Description: "Implicit IAM Role created for MongoDB function"
|
48
|
+
Value: !GetAtt MongoDBFunctionRole.Arn
|