mongo 2.13.0.beta1 → 2.14.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.tar.gz.sig +1 -5
- data/Rakefile +50 -9
- data/lib/mongo.rb +13 -2
- data/lib/mongo/address.rb +1 -1
- data/lib/mongo/address/ipv4.rb +1 -1
- data/lib/mongo/address/ipv6.rb +1 -1
- data/lib/mongo/auth/aws/request.rb +31 -5
- data/lib/mongo/bulk_write.rb +18 -0
- data/lib/mongo/caching_cursor.rb +74 -0
- data/lib/mongo/client.rb +238 -31
- data/lib/mongo/cluster.rb +56 -20
- data/lib/mongo/cluster/sdam_flow.rb +13 -10
- data/lib/mongo/cluster/topology/replica_set_no_primary.rb +3 -2
- data/lib/mongo/cluster/topology/sharded.rb +1 -1
- data/lib/mongo/cluster/topology/single.rb +2 -2
- data/lib/mongo/collection.rb +66 -24
- data/lib/mongo/collection/view.rb +24 -20
- data/lib/mongo/collection/view/aggregation.rb +25 -4
- data/lib/mongo/collection/view/builder/find_command.rb +38 -18
- data/lib/mongo/collection/view/explainable.rb +27 -8
- data/lib/mongo/collection/view/iterable.rb +72 -12
- data/lib/mongo/collection/view/readable.rb +19 -3
- data/lib/mongo/collection/view/writable.rb +55 -5
- data/lib/mongo/crypt/encryption_io.rb +6 -6
- data/lib/mongo/cursor.rb +16 -3
- data/lib/mongo/database.rb +37 -4
- data/lib/mongo/database/view.rb +18 -3
- data/lib/mongo/distinguishing_semaphore.rb +55 -0
- data/lib/mongo/error.rb +5 -0
- data/lib/mongo/error/invalid_read_concern.rb +28 -0
- data/lib/mongo/error/invalid_server_auth_host.rb +22 -0
- data/lib/mongo/error/invalid_session.rb +2 -1
- data/lib/mongo/error/operation_failure.rb +11 -5
- data/lib/mongo/error/server_certificate_revoked.rb +22 -0
- data/lib/mongo/error/sessions_not_supported.rb +35 -0
- data/lib/mongo/error/unsupported_option.rb +14 -12
- data/lib/mongo/event/base.rb +6 -0
- data/lib/mongo/grid/file.rb +5 -0
- data/lib/mongo/grid/file/chunk.rb +2 -0
- data/lib/mongo/grid/fs_bucket.rb +15 -13
- data/lib/mongo/grid/stream/write.rb +9 -3
- data/lib/mongo/index/view.rb +3 -0
- data/lib/mongo/lint.rb +2 -1
- data/lib/mongo/logger.rb +3 -3
- data/lib/mongo/monitoring.rb +38 -0
- data/lib/mongo/monitoring/command_log_subscriber.rb +10 -2
- data/lib/mongo/monitoring/event/command_failed.rb +11 -0
- data/lib/mongo/monitoring/event/command_started.rb +37 -2
- data/lib/mongo/monitoring/event/command_succeeded.rb +11 -0
- data/lib/mongo/monitoring/event/server_closed.rb +1 -1
- data/lib/mongo/monitoring/event/server_description_changed.rb +27 -4
- data/lib/mongo/monitoring/event/server_heartbeat_failed.rb +9 -2
- data/lib/mongo/monitoring/event/server_heartbeat_started.rb +9 -2
- data/lib/mongo/monitoring/event/server_heartbeat_succeeded.rb +9 -2
- data/lib/mongo/monitoring/event/server_opening.rb +1 -1
- data/lib/mongo/monitoring/event/topology_changed.rb +1 -1
- data/lib/mongo/monitoring/event/topology_closed.rb +1 -1
- data/lib/mongo/monitoring/event/topology_opening.rb +1 -1
- data/lib/mongo/monitoring/publishable.rb +6 -3
- data/lib/mongo/monitoring/server_description_changed_log_subscriber.rb +9 -1
- data/lib/mongo/monitoring/topology_changed_log_subscriber.rb +1 -1
- data/lib/mongo/operation.rb +2 -0
- data/lib/mongo/operation/aggregate/result.rb +9 -8
- data/lib/mongo/operation/collections_info/command.rb +5 -0
- data/lib/mongo/operation/collections_info/result.rb +18 -1
- data/lib/mongo/operation/delete/bulk_result.rb +2 -0
- data/lib/mongo/operation/delete/result.rb +3 -0
- data/lib/mongo/operation/explain/command.rb +4 -0
- data/lib/mongo/operation/explain/legacy.rb +4 -0
- data/lib/mongo/operation/explain/op_msg.rb +6 -0
- data/lib/mongo/operation/explain/result.rb +3 -0
- data/lib/mongo/operation/find/legacy/result.rb +2 -0
- data/lib/mongo/operation/find/result.rb +13 -0
- data/lib/mongo/operation/get_more/result.rb +3 -0
- data/lib/mongo/operation/indexes/result.rb +5 -0
- data/lib/mongo/operation/insert/bulk_result.rb +5 -0
- data/lib/mongo/operation/insert/result.rb +5 -0
- data/lib/mongo/operation/list_collections/result.rb +5 -0
- data/lib/mongo/operation/map_reduce/result.rb +10 -0
- data/lib/mongo/operation/parallel_scan/result.rb +4 -0
- data/lib/mongo/operation/result.rb +35 -6
- data/lib/mongo/operation/shared/bypass_document_validation.rb +1 -0
- data/lib/mongo/operation/shared/causal_consistency_supported.rb +1 -0
- data/lib/mongo/operation/shared/collections_info_or_list_collections.rb +2 -0
- data/lib/mongo/operation/shared/executable.rb +1 -0
- data/lib/mongo/operation/shared/idable.rb +2 -1
- data/lib/mongo/operation/shared/limited.rb +1 -0
- data/lib/mongo/operation/shared/object_id_generator.rb +1 -0
- data/lib/mongo/operation/shared/result/aggregatable.rb +1 -0
- data/lib/mongo/operation/shared/sessions_supported.rb +1 -0
- data/lib/mongo/operation/shared/specifiable.rb +1 -0
- data/lib/mongo/operation/shared/write.rb +1 -0
- data/lib/mongo/operation/shared/write_concern_supported.rb +1 -0
- data/lib/mongo/operation/update/legacy/result.rb +7 -0
- data/lib/mongo/operation/update/result.rb +8 -0
- data/lib/mongo/operation/users_info/result.rb +3 -0
- data/lib/mongo/protocol/message.rb +47 -10
- data/lib/mongo/protocol/msg.rb +34 -1
- data/lib/mongo/protocol/query.rb +36 -0
- data/lib/mongo/protocol/serializers.rb +5 -2
- data/lib/mongo/query_cache.rb +242 -0
- data/lib/mongo/retryable.rb +8 -1
- data/lib/mongo/server.rb +15 -4
- data/lib/mongo/server/app_metadata.rb +27 -3
- data/lib/mongo/server/connection.rb +4 -4
- data/lib/mongo/server/connection_base.rb +38 -12
- data/lib/mongo/server/connection_common.rb +2 -2
- data/lib/mongo/server/connection_pool.rb +3 -0
- data/lib/mongo/server/description.rb +13 -1
- data/lib/mongo/server/monitor.rb +76 -44
- data/lib/mongo/server/monitor/connection.rb +57 -9
- data/lib/mongo/server/pending_connection.rb +14 -4
- data/lib/mongo/server/push_monitor.rb +173 -0
- data/{spec/runners/transactions/context.rb → lib/mongo/server/push_monitor/connection.rb} +9 -14
- data/lib/mongo/server_selector.rb +0 -1
- data/lib/mongo/server_selector/base.rb +583 -1
- data/lib/mongo/server_selector/nearest.rb +1 -6
- data/lib/mongo/server_selector/primary.rb +1 -6
- data/lib/mongo/server_selector/primary_preferred.rb +7 -10
- data/lib/mongo/server_selector/secondary.rb +1 -6
- data/lib/mongo/server_selector/secondary_preferred.rb +1 -7
- data/lib/mongo/session.rb +7 -1
- data/lib/mongo/socket.rb +26 -12
- data/lib/mongo/socket/ocsp_cache.rb +97 -0
- data/lib/mongo/socket/ocsp_verifier.rb +368 -0
- data/lib/mongo/socket/ssl.rb +46 -25
- data/lib/mongo/socket/tcp.rb +1 -1
- data/lib/mongo/srv/monitor.rb +7 -13
- data/lib/mongo/srv/resolver.rb +14 -10
- data/lib/mongo/timeout.rb +2 -0
- data/lib/mongo/topology_version.rb +9 -0
- data/lib/mongo/uri.rb +21 -390
- data/lib/mongo/uri/options_mapper.rb +582 -0
- data/lib/mongo/uri/srv_protocol.rb +3 -2
- data/lib/mongo/utils.rb +73 -0
- data/lib/mongo/version.rb +1 -1
- data/spec/NOTES.aws-auth.md +12 -7
- data/spec/README.aws-auth.md +2 -2
- data/spec/README.md +63 -1
- data/spec/integration/awaited_ismaster_spec.rb +28 -0
- data/spec/integration/bson_symbol_spec.rb +4 -2
- data/spec/integration/bulk_write_spec.rb +67 -0
- data/spec/integration/change_stream_examples_spec.rb +6 -2
- data/spec/integration/change_stream_spec.rb +1 -1
- data/spec/integration/check_clean_slate_spec.rb +16 -0
- data/spec/integration/client_authentication_options_spec.rb +92 -28
- data/spec/integration/client_construction_spec.rb +1 -0
- data/spec/integration/client_side_encryption/auto_encryption_bulk_writes_spec.rb +9 -5
- data/spec/integration/connect_single_rs_name_spec.rb +5 -2
- data/spec/integration/connection_pool_populator_spec.rb +4 -2
- data/spec/integration/connection_spec.rb +7 -4
- data/spec/integration/crud_spec.rb +4 -4
- data/spec/integration/cursor_reaping_spec.rb +54 -18
- data/spec/integration/docs_examples_spec.rb +6 -0
- data/spec/integration/fork_reconnect_spec.rb +56 -1
- data/spec/integration/grid_fs_bucket_spec.rb +48 -0
- data/spec/integration/heartbeat_events_spec.rb +4 -23
- data/spec/integration/ocsp_connectivity_spec.rb +26 -0
- data/spec/integration/ocsp_verifier_cache_spec.rb +188 -0
- data/spec/integration/ocsp_verifier_spec.rb +334 -0
- data/spec/integration/query_cache_spec.rb +1045 -0
- data/spec/integration/query_cache_transactions_spec.rb +190 -0
- data/spec/integration/read_concern_spec.rb +1 -1
- data/spec/integration/retryable_errors_spec.rb +1 -1
- data/spec/integration/retryable_writes/retryable_writes_40_and_newer_spec.rb +1 -0
- data/spec/integration/retryable_writes/shared/performs_legacy_retries.rb +4 -2
- data/spec/integration/retryable_writes/shared/performs_modern_retries.rb +3 -3
- data/spec/integration/retryable_writes/shared/performs_no_retries.rb +2 -2
- data/spec/integration/sdam_error_handling_spec.rb +122 -15
- data/spec/integration/sdam_events_spec.rb +80 -6
- data/spec/integration/sdam_prose_spec.rb +64 -0
- data/spec/integration/server_monitor_spec.rb +25 -1
- data/spec/integration/server_selection_spec.rb +36 -0
- data/spec/integration/size_limit_spec.rb +23 -5
- data/spec/integration/srv_monitoring_spec.rb +38 -3
- data/spec/integration/srv_spec.rb +56 -0
- data/spec/integration/ssl_uri_options_spec.rb +2 -2
- data/spec/integration/transactions_examples_spec.rb +17 -7
- data/spec/integration/zlib_compression_spec.rb +25 -0
- data/spec/lite_spec_helper.rb +20 -9
- data/spec/mongo/address_spec.rb +1 -1
- data/spec/mongo/auth/aws/request_region_spec.rb +42 -0
- data/spec/mongo/auth/aws/request_spec.rb +76 -0
- data/spec/mongo/auth/scram_spec.rb +1 -1
- data/spec/mongo/auth/user_spec.rb +1 -1
- data/spec/mongo/bulk_write_spec.rb +2 -2
- data/spec/mongo/caching_cursor_spec.rb +70 -0
- data/spec/mongo/client_construction_spec.rb +386 -3
- data/spec/mongo/client_encryption_spec.rb +16 -10
- data/spec/mongo/client_spec.rb +85 -3
- data/spec/mongo/cluster/topology/replica_set_spec.rb +53 -10
- data/spec/mongo/cluster/topology/sharded_spec.rb +1 -1
- data/spec/mongo/cluster/topology/single_spec.rb +19 -8
- data/spec/mongo/cluster/topology/unknown_spec.rb +1 -1
- data/spec/mongo/cluster/topology_spec.rb +1 -1
- data/spec/mongo/cluster_spec.rb +37 -35
- data/spec/mongo/collection/view/change_stream_resume_spec.rb +7 -7
- data/spec/mongo/collection/view/explainable_spec.rb +87 -4
- data/spec/mongo/collection/view/map_reduce_spec.rb +2 -0
- data/spec/mongo/collection/view/readable_spec.rb +36 -0
- data/spec/mongo/collection_spec.rb +572 -0
- data/spec/mongo/crypt/auto_decryption_context_spec.rb +1 -1
- data/spec/mongo/crypt/auto_encryption_context_spec.rb +1 -1
- data/spec/mongo/crypt/binary_spec.rb +1 -6
- data/spec/mongo/crypt/binding/binary_spec.rb +1 -6
- data/spec/mongo/crypt/binding/context_spec.rb +2 -7
- data/spec/mongo/crypt/binding/helpers_spec.rb +1 -6
- data/spec/mongo/crypt/binding/mongocrypt_spec.rb +2 -7
- data/spec/mongo/crypt/binding/status_spec.rb +1 -6
- data/spec/mongo/crypt/binding/version_spec.rb +1 -6
- data/spec/mongo/crypt/data_key_context_spec.rb +1 -1
- data/spec/mongo/crypt/explicit_decryption_context_spec.rb +1 -1
- data/spec/mongo/crypt/explicit_encryption_context_spec.rb +1 -1
- data/spec/mongo/crypt/status_spec.rb +1 -6
- data/spec/mongo/database_spec.rb +353 -8
- data/spec/mongo/distinguishing_semaphore_spec.rb +63 -0
- data/spec/mongo/error/no_server_available_spec.rb +1 -1
- data/spec/mongo/error/operation_failure_spec.rb +40 -0
- data/spec/mongo/index/view_spec.rb +148 -2
- data/spec/mongo/logger_spec.rb +13 -11
- data/spec/mongo/monitoring/event/server_closed_spec.rb +1 -1
- data/spec/mongo/monitoring/event/server_description_changed_spec.rb +1 -4
- data/spec/mongo/monitoring/event/server_opening_spec.rb +1 -1
- data/spec/mongo/monitoring/event/topology_changed_spec.rb +1 -1
- data/spec/mongo/monitoring/event/topology_closed_spec.rb +1 -1
- data/spec/mongo/monitoring/event/topology_opening_spec.rb +1 -1
- data/spec/mongo/operation/delete/op_msg_spec.rb +3 -3
- data/spec/mongo/operation/insert/command_spec.rb +2 -2
- data/spec/mongo/operation/insert/op_msg_spec.rb +3 -3
- data/spec/mongo/operation/read_preference_op_msg_spec.rb +1 -1
- data/spec/mongo/operation/update/command_spec.rb +2 -2
- data/spec/mongo/operation/update/op_msg_spec.rb +3 -3
- data/spec/mongo/protocol/msg_spec.rb +10 -0
- data/spec/mongo/query_cache_spec.rb +280 -0
- data/spec/mongo/semaphore_spec.rb +51 -0
- data/spec/mongo/server/app_metadata_shared.rb +82 -2
- data/spec/mongo/server/connection_auth_spec.rb +2 -2
- data/spec/mongo/server/connection_pool_spec.rb +7 -3
- data/spec/mongo/server/connection_spec.rb +15 -8
- data/spec/mongo/server/description_spec.rb +18 -0
- data/spec/mongo/server_selector/nearest_spec.rb +23 -23
- data/spec/mongo/server_selector/primary_preferred_spec.rb +26 -26
- data/spec/mongo/server_selector/primary_spec.rb +9 -9
- data/spec/mongo/server_selector/secondary_preferred_spec.rb +22 -22
- data/spec/mongo/server_selector/secondary_spec.rb +18 -18
- data/spec/mongo/server_selector_spec.rb +6 -6
- data/spec/mongo/session_spec.rb +35 -0
- data/spec/mongo/socket/ssl_spec.rb +4 -4
- data/spec/mongo/socket_spec.rb +1 -1
- data/spec/mongo/uri/srv_protocol_spec.rb +64 -33
- data/spec/mongo/uri_option_parsing_spec.rb +11 -11
- data/spec/mongo/uri_spec.rb +68 -41
- data/spec/mongo/utils_spec.rb +39 -0
- data/spec/runners/auth.rb +3 -0
- data/spec/runners/change_streams/test.rb +3 -3
- data/spec/runners/cmap.rb +1 -1
- data/spec/runners/command_monitoring.rb +3 -34
- data/spec/runners/connection_string.rb +35 -124
- data/spec/runners/crud/context.rb +9 -5
- data/spec/runners/crud/operation.rb +59 -27
- data/spec/runners/crud/spec.rb +0 -8
- data/spec/runners/crud/test.rb +1 -1
- data/spec/runners/crud/test_base.rb +0 -19
- data/spec/runners/sdam.rb +2 -2
- data/spec/runners/server_selection.rb +242 -28
- data/spec/runners/transactions.rb +12 -12
- data/spec/runners/transactions/operation.rb +151 -25
- data/spec/runners/transactions/test.rb +62 -18
- data/spec/shared/LICENSE +20 -0
- data/spec/shared/lib/mrss/child_process_helper.rb +80 -0
- data/spec/shared/lib/mrss/constraints.rb +303 -0
- data/spec/shared/lib/mrss/lite_constraints.rb +175 -0
- data/spec/shared/lib/mrss/spec_organizer.rb +149 -0
- data/spec/spec_helper.rb +3 -1
- data/spec/spec_tests/cmap_spec.rb +7 -3
- data/spec/spec_tests/command_monitoring_spec.rb +22 -12
- data/spec/spec_tests/crud_spec.rb +1 -1
- data/spec/spec_tests/data/change_streams/change-streams-errors.yml +4 -9
- data/spec/spec_tests/data/change_streams/change-streams-resume-whitelist.yml +66 -0
- data/spec/spec_tests/data/change_streams/change-streams.yml +0 -1
- data/spec/spec_tests/data/cmap/pool-checkout-connection.yml +6 -2
- data/spec/spec_tests/data/cmap/pool-create-min-size.yml +3 -0
- data/spec/spec_tests/data/connection_string/valid-warnings.yml +24 -0
- data/spec/spec_tests/data/max_staleness/ReplicaSetNoPrimary/MaxStalenessTooSmall.yml +15 -0
- data/spec/spec_tests/data/max_staleness/ReplicaSetNoPrimary/NoKnownServers.yml +4 -3
- data/spec/spec_tests/data/max_staleness/Unknown/SmallMaxStaleness.yml +1 -0
- data/spec/spec_tests/data/sdam_integration/cancel-server-check.yml +96 -0
- data/spec/spec_tests/data/sdam_integration/connectTimeoutMS.yml +88 -0
- data/spec/spec_tests/data/sdam_integration/find-network-error.yml +83 -0
- data/spec/spec_tests/data/sdam_integration/find-shutdown-error.yml +116 -0
- data/spec/spec_tests/data/sdam_integration/insert-network-error.yml +86 -0
- data/spec/spec_tests/data/sdam_integration/insert-shutdown-error.yml +115 -0
- data/spec/spec_tests/data/sdam_integration/isMaster-command-error.yml +168 -0
- data/spec/spec_tests/data/sdam_integration/isMaster-network-error.yml +162 -0
- data/spec/spec_tests/data/sdam_integration/isMaster-timeout.yml +229 -0
- data/spec/spec_tests/data/sdam_integration/rediscover-quickly-after-step-down.yml +87 -0
- data/spec/spec_tests/data/sdam_monitoring/discovered_standalone.yml +1 -3
- data/spec/spec_tests/data/sdam_monitoring/standalone.yml +2 -2
- data/spec/spec_tests/data/sdam_monitoring/standalone_repeated.yml +2 -2
- data/spec/spec_tests/data/sdam_monitoring/standalone_suppress_equal_description_changes.yml +2 -2
- data/spec/spec_tests/data/sdam_monitoring/standalone_to_rs_with_me_mismatch.yml +2 -2
- data/spec/spec_tests/data/uri_options/auth-options.yml +25 -0
- data/spec/spec_tests/data/uri_options/compression-options.yml +6 -3
- data/spec/spec_tests/data/uri_options/read-preference-options.yml +24 -0
- data/spec/spec_tests/data/uri_options/ruby-connection-options.yml +1 -0
- data/spec/spec_tests/data/uri_options/tls-options.yml +160 -4
- data/spec/spec_tests/dns_seedlist_discovery_spec.rb +9 -1
- data/spec/spec_tests/max_staleness_spec.rb +4 -142
- data/spec/spec_tests/retryable_reads_spec.rb +2 -2
- data/spec/spec_tests/sdam_integration_spec.rb +13 -0
- data/spec/spec_tests/sdam_monitoring_spec.rb +1 -2
- data/spec/spec_tests/server_selection_spec.rb +4 -116
- data/spec/spec_tests/uri_options_spec.rb +31 -33
- data/spec/stress/cleanup_spec.rb +17 -2
- data/spec/stress/connection_pool_stress_spec.rb +10 -8
- data/spec/stress/fork_reconnect_stress_spec.rb +1 -1
- data/spec/support/certificates/atlas-ocsp-ca.crt +28 -0
- data/spec/support/certificates/atlas-ocsp.crt +41 -0
- data/spec/support/client_registry.rb +1 -0
- data/spec/support/client_registry_macros.rb +11 -2
- data/spec/support/cluster_config.rb +4 -0
- data/spec/support/common_shortcuts.rb +45 -0
- data/spec/support/constraints.rb +6 -253
- data/spec/support/event_subscriber.rb +123 -33
- data/spec/support/keyword_struct.rb +26 -0
- data/spec/support/matchers.rb +16 -0
- data/spec/support/ocsp +1 -0
- data/spec/support/session_registry.rb +52 -0
- data/spec/support/shared/server_selector.rb +13 -1
- data/spec/support/spec_config.rb +60 -13
- data/spec/support/spec_setup.rb +1 -1
- data/spec/support/utils.rb +84 -1
- metadata +1027 -937
- metadata.gz.sig +0 -0
- data/lib/mongo/server_selector/selectable.rb +0 -560
- data/spec/runners/sdam_monitoring.rb +0 -89
- data/spec/support/lite_constraints.rb +0 -141
@@ -0,0 +1,582 @@
|
|
1
|
+
# Copyright (C) 2020 MongoDB Inc.
|
2
|
+
#
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the 'License');
|
4
|
+
# you may not use this file except in compliance with the License.
|
5
|
+
# You may obtain a copy of the License at
|
6
|
+
#
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
#
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
10
|
+
# distributed under the License is distributed on an 'AS IS' BASIS,
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
# See the License for the specific language governing permissions and
|
13
|
+
# limitations under the License.
|
14
|
+
|
15
|
+
module Mongo
|
16
|
+
class URI
|
17
|
+
|
18
|
+
# Performs mapping between URI options and Ruby options.
|
19
|
+
#
|
20
|
+
# This class contains:
|
21
|
+
#
|
22
|
+
# - The mapping defining how URI options are converted to Ruby options.
|
23
|
+
# - The mapping from downcased URI option names to canonical-cased URI
|
24
|
+
# option names.
|
25
|
+
# - Methods to perform conversion of URI option values to Ruby option
|
26
|
+
# values (the convert_* methods). These generally warn and return nil
|
27
|
+
# when input given is invalid.
|
28
|
+
# - Methods to perform conversion of Ruby option values to standardized
|
29
|
+
# MongoClient options (revert_* methods). These assume the input is valid
|
30
|
+
# and generally do not perform validation.
|
31
|
+
#
|
32
|
+
# URI option names are case insensitive. Ruby options are specified as
|
33
|
+
# symbols (though in Client options use indifferent access).
|
34
|
+
#
|
35
|
+
# @api private
|
36
|
+
class OptionsMapper
|
37
|
+
|
38
|
+
include Loggable
|
39
|
+
|
40
|
+
# Instantates the options mapper.
|
41
|
+
#
|
42
|
+
# @option opts [ Logger ] :logger A custom logger to use.
|
43
|
+
def initialize(**opts)
|
44
|
+
@options = opts
|
45
|
+
end
|
46
|
+
|
47
|
+
# @return [ Hash ] The options.
|
48
|
+
attr_reader :options
|
49
|
+
|
50
|
+
# Adds an option to the uri options hash.
|
51
|
+
#
|
52
|
+
# Acquires a target for the option based on group.
|
53
|
+
# Transforms the value.
|
54
|
+
# Merges the option into the target.
|
55
|
+
#
|
56
|
+
# @param [ String ] key URI option name.
|
57
|
+
# @param [ String ] value The value of the option.
|
58
|
+
# @param [ Hash ] uri_options The base option target.
|
59
|
+
def add_uri_option(key, value, uri_options)
|
60
|
+
strategy = URI_OPTION_MAP[key.downcase]
|
61
|
+
if strategy.nil?
|
62
|
+
log_warn("Unsupported URI option '#{key}' on URI '#{@string}'. It will be ignored.")
|
63
|
+
return
|
64
|
+
end
|
65
|
+
|
66
|
+
group = strategy[:group]
|
67
|
+
target = if group
|
68
|
+
uri_options[group] || {}
|
69
|
+
else
|
70
|
+
uri_options
|
71
|
+
end
|
72
|
+
value = apply_transform(key, value, strategy[:type])
|
73
|
+
# Sometimes the value here would be nil, for example if we are processing
|
74
|
+
# read preference tags or auth mechanism properties and all of the
|
75
|
+
# data within is invalid. Ignore such options.
|
76
|
+
unless value.nil?
|
77
|
+
merge_uri_option(target, value, strategy[:name])
|
78
|
+
end
|
79
|
+
|
80
|
+
if group && !target.empty? && !uri_options.key?(group)
|
81
|
+
uri_options[group] = target
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
# Converts Ruby options provided to "standardized MongoClient options".
|
86
|
+
#
|
87
|
+
# @param [ Hash ] opts Ruby options to convert.
|
88
|
+
#
|
89
|
+
# @return [ Hash ] Standardized MongoClient options.
|
90
|
+
def ruby_to_smc(opts)
|
91
|
+
rv = {}
|
92
|
+
URI_OPTION_MAP.each do |uri_key, spec|
|
93
|
+
if spec[:group]
|
94
|
+
v = opts[spec[:group]]
|
95
|
+
v = v && v[spec[:name]]
|
96
|
+
else
|
97
|
+
v = opts[spec[:name]]
|
98
|
+
end
|
99
|
+
unless v.nil?
|
100
|
+
if spec[:type]
|
101
|
+
v = send("revert_#{spec[:type]}", v)
|
102
|
+
end
|
103
|
+
canonical_key = URI_OPTION_CANONICAL_NAMES[uri_key]
|
104
|
+
unless canonical_key
|
105
|
+
raise ArgumentError, "Option #{uri_key} is not known"
|
106
|
+
end
|
107
|
+
rv[canonical_key] = v
|
108
|
+
end
|
109
|
+
end
|
110
|
+
# For options that default to true, remove the value if it is true.
|
111
|
+
%w(retryReads retryWrites).each do |k|
|
112
|
+
if rv[k]
|
113
|
+
rv.delete(k)
|
114
|
+
end
|
115
|
+
end
|
116
|
+
# Remove auth source when it is $external for mechanisms that default
|
117
|
+
# (or require) that auth source.
|
118
|
+
if %w(MONGODB-AWS).include?(rv['authMechanism']) && rv['authSource'] == '$external'
|
119
|
+
rv.delete('authSource')
|
120
|
+
end
|
121
|
+
# ssl and tls are aliases, remove ssl ones
|
122
|
+
rv.delete('ssl')
|
123
|
+
# TODO remove authSource if it is the same as the database,
|
124
|
+
# requires this method to know the database specified in the client.
|
125
|
+
rv
|
126
|
+
end
|
127
|
+
|
128
|
+
private
|
129
|
+
|
130
|
+
# Applies URI value transformation by either using the default cast
|
131
|
+
# or a transformation appropriate for the given type.
|
132
|
+
#
|
133
|
+
# @param key [String] URI option name.
|
134
|
+
# @param value [String] The value to be transformed.
|
135
|
+
# @param type [Symbol] The transform method.
|
136
|
+
def apply_transform(key, value, type)
|
137
|
+
if type
|
138
|
+
send("convert_#{type}", key, value)
|
139
|
+
else
|
140
|
+
value
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
# Merges a new option into the target.
|
145
|
+
#
|
146
|
+
# If the option exists at the target destination the merge will
|
147
|
+
# be an addition.
|
148
|
+
#
|
149
|
+
# Specifically required to append an additional tag set
|
150
|
+
# to the array of tag sets without overwriting the original.
|
151
|
+
#
|
152
|
+
# @param target [Hash] The destination.
|
153
|
+
# @param value [Object] The value to be merged.
|
154
|
+
# @param name [Symbol] The name of the option.
|
155
|
+
def merge_uri_option(target, value, name)
|
156
|
+
if target.key?(name)
|
157
|
+
if REPEATABLE_OPTIONS.include?(name)
|
158
|
+
target[name] += value
|
159
|
+
else
|
160
|
+
log_warn("Repeated option key: #{name}.")
|
161
|
+
end
|
162
|
+
else
|
163
|
+
target.merge!(name => value)
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
# Hash for storing map of URI option parameters to conversion strategies
|
168
|
+
URI_OPTION_MAP = {}
|
169
|
+
|
170
|
+
# @return [ Hash<String, String> ] Map from lowercased to canonical URI
|
171
|
+
# option names.
|
172
|
+
URI_OPTION_CANONICAL_NAMES = {}
|
173
|
+
|
174
|
+
# Simple internal dsl to register a MongoDB URI option in the URI_OPTION_MAP.
|
175
|
+
#
|
176
|
+
# @param uri_key [String] The MongoDB URI option to register.
|
177
|
+
# @param name [Symbol] The name of the option in the driver.
|
178
|
+
# @param extra [Hash] Extra options.
|
179
|
+
# * :group [Symbol] Nested hash where option will go.
|
180
|
+
# * :type [Symbol] Name of function to transform value.
|
181
|
+
def self.uri_option(uri_key, name, **extra)
|
182
|
+
URI_OPTION_MAP[uri_key.downcase] = { name: name }.update(extra)
|
183
|
+
URI_OPTION_CANONICAL_NAMES[uri_key.downcase] = uri_key
|
184
|
+
end
|
185
|
+
|
186
|
+
# Replica Set Options
|
187
|
+
uri_option 'replicaSet', :replica_set
|
188
|
+
|
189
|
+
# Timeout Options
|
190
|
+
uri_option 'connectTimeoutMS', :connect_timeout, type: :ms
|
191
|
+
uri_option 'socketTimeoutMS', :socket_timeout, type: :ms
|
192
|
+
uri_option 'serverSelectionTimeoutMS', :server_selection_timeout, type: :ms
|
193
|
+
uri_option 'localThresholdMS', :local_threshold, type: :ms
|
194
|
+
uri_option 'heartbeatFrequencyMS', :heartbeat_frequency, type: :ms
|
195
|
+
uri_option 'maxIdleTimeMS', :max_idle_time, type: :ms
|
196
|
+
|
197
|
+
# Write Options
|
198
|
+
uri_option 'w', :w, group: :write_concern, type: :w
|
199
|
+
uri_option 'journal', :j, group: :write_concern, type: :bool
|
200
|
+
uri_option 'fsync', :fsync, group: :write_concern, type: :bool
|
201
|
+
uri_option 'wTimeoutMS', :wtimeout, group: :write_concern, type: :integer
|
202
|
+
|
203
|
+
# Read Options
|
204
|
+
uri_option 'readPreference', :mode, group: :read, type: :read_mode
|
205
|
+
uri_option 'readPreferenceTags', :tag_sets, group: :read, type: :read_tags
|
206
|
+
uri_option 'maxStalenessSeconds', :max_staleness, group: :read, type: :max_staleness
|
207
|
+
|
208
|
+
# Pool options
|
209
|
+
uri_option 'minPoolSize', :min_pool_size, type: :integer
|
210
|
+
uri_option 'maxPoolSize', :max_pool_size, type: :integer
|
211
|
+
uri_option 'waitQueueTimeoutMS', :wait_queue_timeout, type: :ms
|
212
|
+
|
213
|
+
# Security Options
|
214
|
+
uri_option 'ssl', :ssl, type: :repeated_bool
|
215
|
+
uri_option 'tls', :ssl, type: :repeated_bool
|
216
|
+
uri_option 'tlsAllowInvalidCertificates', :ssl_verify_certificate,
|
217
|
+
type: :inverse_bool
|
218
|
+
uri_option 'tlsAllowInvalidHostnames', :ssl_verify_hostname,
|
219
|
+
type: :inverse_bool
|
220
|
+
uri_option 'tlsCAFile', :ssl_ca_cert
|
221
|
+
uri_option 'tlsCertificateKeyFile', :ssl_cert
|
222
|
+
uri_option 'tlsCertificateKeyFilePassword', :ssl_key_pass_phrase
|
223
|
+
uri_option 'tlsInsecure', :ssl_verify, type: :inverse_bool
|
224
|
+
uri_option 'tlsDisableOCSPEndpointCheck', :ssl_verify_ocsp_endpoint,
|
225
|
+
type: :inverse_bool
|
226
|
+
|
227
|
+
# Topology options
|
228
|
+
uri_option 'directConnection', :direct_connection, type: :bool
|
229
|
+
uri_option 'connect', :connect, type: :symbol
|
230
|
+
|
231
|
+
# Auth Options
|
232
|
+
uri_option 'authSource', :auth_source
|
233
|
+
uri_option 'authMechanism', :auth_mech, type: :auth_mech
|
234
|
+
uri_option 'authMechanismProperties', :auth_mech_properties, type: :auth_mech_props
|
235
|
+
|
236
|
+
# Client Options
|
237
|
+
uri_option 'appName', :app_name
|
238
|
+
uri_option 'compressors', :compressors, type: :array
|
239
|
+
uri_option 'readConcernLevel', :level, group: :read_concern, type: :symbol
|
240
|
+
uri_option 'retryReads', :retry_reads, type: :bool
|
241
|
+
uri_option 'retryWrites', :retry_writes, type: :bool
|
242
|
+
uri_option 'zlibCompressionLevel', :zlib_compression_level, type: :zlib_compression_level
|
243
|
+
|
244
|
+
# Converts +value+ to a boolean.
|
245
|
+
#
|
246
|
+
# Returns true for 'true', false for 'false', otherwise nil.
|
247
|
+
#
|
248
|
+
# @param [ String ] name Name of the URI option being processed.
|
249
|
+
# @param value [ String ] URI option value.
|
250
|
+
#
|
251
|
+
# @return [ true | false | nil ] Converted value.
|
252
|
+
def convert_bool(name, value)
|
253
|
+
case value
|
254
|
+
when "true", 'TRUE'
|
255
|
+
true
|
256
|
+
when "false", 'FALSE'
|
257
|
+
false
|
258
|
+
else
|
259
|
+
log_warn("invalid boolean option for #{name}: #{value}")
|
260
|
+
nil
|
261
|
+
end
|
262
|
+
end
|
263
|
+
|
264
|
+
def revert_bool(value)
|
265
|
+
value
|
266
|
+
end
|
267
|
+
|
268
|
+
# Converts the value into a boolean and returns it wrapped in an array.
|
269
|
+
#
|
270
|
+
# @param name [ String ] Name of the URI option being processed.
|
271
|
+
# @param value [ String ] URI option value.
|
272
|
+
#
|
273
|
+
# @return [ Array<true | false> ] The boolean value parsed and wraped
|
274
|
+
# in an array.
|
275
|
+
def convert_repeated_bool(name, value)
|
276
|
+
[convert_bool(name, value)]
|
277
|
+
end
|
278
|
+
|
279
|
+
def revert_repeated_bool(value)
|
280
|
+
value
|
281
|
+
end
|
282
|
+
|
283
|
+
# Parses a boolean value and returns its inverse.
|
284
|
+
#
|
285
|
+
# @param [ String ] name Name of the URI option being processed.
|
286
|
+
# @param value [ String ] The URI option value.
|
287
|
+
#
|
288
|
+
# @return [ true | false | nil ] The inverse of the boolean value parsed out, otherwise nil
|
289
|
+
# (and a warning will be logged).
|
290
|
+
def convert_inverse_bool(name, value)
|
291
|
+
b = convert_bool(name, value)
|
292
|
+
|
293
|
+
if b.nil?
|
294
|
+
nil
|
295
|
+
else
|
296
|
+
!b
|
297
|
+
end
|
298
|
+
end
|
299
|
+
|
300
|
+
def revert_inverse_bool(value)
|
301
|
+
!value
|
302
|
+
end
|
303
|
+
|
304
|
+
# Converts +value+ into an integer.
|
305
|
+
#
|
306
|
+
# If the value is not a valid integer, warns and returns nil.
|
307
|
+
#
|
308
|
+
# @param [ String ] name Name of the URI option being processed.
|
309
|
+
# @param value [ String ] URI option value.
|
310
|
+
#
|
311
|
+
# @return [ nil | Integer ] Converted value.
|
312
|
+
def convert_integer(name, value)
|
313
|
+
unless /\A\d+\z/ =~ value
|
314
|
+
log_warn("#{value} is not a valid integer for #{name}")
|
315
|
+
return nil
|
316
|
+
end
|
317
|
+
|
318
|
+
value.to_i
|
319
|
+
end
|
320
|
+
|
321
|
+
def revert_integer(value)
|
322
|
+
value
|
323
|
+
end
|
324
|
+
|
325
|
+
# Ruby's convention is to provide timeouts in seconds, not milliseconds and
|
326
|
+
# to use fractions where more precision is necessary. The connection string
|
327
|
+
# options are always in MS so we provide an easy conversion type.
|
328
|
+
#
|
329
|
+
# @param [ String ] name Name of the URI option being processed.
|
330
|
+
# @param [ Integer ] value The millisecond value.
|
331
|
+
#
|
332
|
+
# @return [ Float ] The seconds value.
|
333
|
+
#
|
334
|
+
# @since 2.0.0
|
335
|
+
def convert_ms(name, value)
|
336
|
+
unless /\A-?\d+(\.\d+)?\z/ =~ value
|
337
|
+
log_warn("Invalid ms value for #{name}: #{value}")
|
338
|
+
return nil
|
339
|
+
end
|
340
|
+
|
341
|
+
if value[0] == '-'
|
342
|
+
log_warn("#{name} cannot be a negative number")
|
343
|
+
return nil
|
344
|
+
end
|
345
|
+
|
346
|
+
value.to_f / 1000
|
347
|
+
end
|
348
|
+
|
349
|
+
def revert_ms(value)
|
350
|
+
(value * 1000).round
|
351
|
+
end
|
352
|
+
|
353
|
+
# Converts +value+ into a symbol.
|
354
|
+
#
|
355
|
+
# @param [ String ] name Name of the URI option being processed.
|
356
|
+
# @param value [ String ] URI option value.
|
357
|
+
#
|
358
|
+
# @return [ Symbol ] Converted value.
|
359
|
+
def convert_symbol(name, value)
|
360
|
+
value.to_sym
|
361
|
+
end
|
362
|
+
|
363
|
+
def revert_symbol(value)
|
364
|
+
value.to_s
|
365
|
+
end
|
366
|
+
|
367
|
+
# Extract values from the string and put them into an array.
|
368
|
+
#
|
369
|
+
# @param [ String ] name Name of the URI option being processed.
|
370
|
+
# @param [ String ] value The string to build an array from.
|
371
|
+
#
|
372
|
+
# @return [ Array ] The array built from the string.
|
373
|
+
def convert_array(name, value)
|
374
|
+
value.split(',')
|
375
|
+
end
|
376
|
+
|
377
|
+
def revert_array(value)
|
378
|
+
value
|
379
|
+
end
|
380
|
+
|
381
|
+
# Authentication mechanism transformation.
|
382
|
+
#
|
383
|
+
# @param [ String ] name Name of the URI option being processed.
|
384
|
+
# @param value [String] The authentication mechanism.
|
385
|
+
#
|
386
|
+
# @return [Symbol] The transformed authentication mechanism.
|
387
|
+
def convert_auth_mech(name, value)
|
388
|
+
(AUTH_MECH_MAP[value.upcase] || value).tap do |mech|
|
389
|
+
log_warn("#{value} is not a valid auth mechanism") unless mech
|
390
|
+
end
|
391
|
+
end
|
392
|
+
|
393
|
+
def revert_auth_mech(value)
|
394
|
+
found = AUTH_MECH_MAP.detect do |k, v|
|
395
|
+
v == value
|
396
|
+
end
|
397
|
+
if found
|
398
|
+
found.first
|
399
|
+
else
|
400
|
+
raise ArgumentError, "Unknown auth mechanism #{value}"
|
401
|
+
end
|
402
|
+
end
|
403
|
+
|
404
|
+
# Auth mechanism properties extractor.
|
405
|
+
#
|
406
|
+
# @param [ String ] name Name of the URI option being processed.
|
407
|
+
# @param value [ String ] The auth mechanism properties string.
|
408
|
+
#
|
409
|
+
# @return [ Hash ] The auth mechanism properties hash.
|
410
|
+
def convert_auth_mech_props(name, value)
|
411
|
+
properties = hash_extractor('authMechanismProperties', value)
|
412
|
+
if properties
|
413
|
+
properties.each do |k, v|
|
414
|
+
if k.to_s.downcase == 'canonicalize_host_name' && v
|
415
|
+
properties[k] = (v.downcase == 'true')
|
416
|
+
end
|
417
|
+
end
|
418
|
+
end
|
419
|
+
properties
|
420
|
+
end
|
421
|
+
|
422
|
+
def revert_auth_mech_props(value)
|
423
|
+
value
|
424
|
+
end
|
425
|
+
|
426
|
+
# Parses the max staleness value, which must be either "0" or an integer
|
427
|
+
# greater or equal to 90.
|
428
|
+
#
|
429
|
+
# @param [ String ] name Name of the URI option being processed.
|
430
|
+
# @param value [ String ] The max staleness string.
|
431
|
+
#
|
432
|
+
# @return [ Integer | nil ] The max staleness integer parsed out if it is valid, otherwise nil
|
433
|
+
# (and a warning will be logged).
|
434
|
+
def convert_max_staleness(name, value)
|
435
|
+
if /\A-?\d+\z/ =~ value
|
436
|
+
int = value.to_i
|
437
|
+
|
438
|
+
if int == -1
|
439
|
+
int = nil
|
440
|
+
end
|
441
|
+
|
442
|
+
if int && (int >= 0 && int < 90 || int < 0)
|
443
|
+
log_warn("max staleness should be either 0 or greater than 90: #{value}")
|
444
|
+
int = nil
|
445
|
+
end
|
446
|
+
|
447
|
+
return int
|
448
|
+
end
|
449
|
+
|
450
|
+
log_warn("Invalid max staleness value: #{value}")
|
451
|
+
nil
|
452
|
+
end
|
453
|
+
|
454
|
+
def revert_max_staleness(value)
|
455
|
+
value
|
456
|
+
end
|
457
|
+
|
458
|
+
# Read preference mode transformation.
|
459
|
+
#
|
460
|
+
# @param [ String ] name Name of the URI option being processed.
|
461
|
+
# @param value [String] The read mode string value.
|
462
|
+
#
|
463
|
+
# @return [Symbol] The read mode symbol.
|
464
|
+
def convert_read_mode(name, value)
|
465
|
+
READ_MODE_MAP[value.downcase] || value
|
466
|
+
end
|
467
|
+
|
468
|
+
def revert_read_mode(value)
|
469
|
+
value.to_s.gsub(/_(\w)/) { $1.upcase }
|
470
|
+
end
|
471
|
+
|
472
|
+
# Read preference tags transformation.
|
473
|
+
#
|
474
|
+
# @param [ String ] name Name of the URI option being processed.
|
475
|
+
# @param value [String] The string representing tag set.
|
476
|
+
#
|
477
|
+
# @return [Array<Hash>] Array with tag set.
|
478
|
+
def convert_read_tags(name, value)
|
479
|
+
converted = convert_read_set(name, value)
|
480
|
+
if converted
|
481
|
+
[converted]
|
482
|
+
else
|
483
|
+
nil
|
484
|
+
end
|
485
|
+
end
|
486
|
+
|
487
|
+
def revert_read_tags(value)
|
488
|
+
value
|
489
|
+
end
|
490
|
+
|
491
|
+
# Read preference tag set extractor.
|
492
|
+
#
|
493
|
+
# @param [ String ] name Name of the URI option being processed.
|
494
|
+
# @param value [String] The tag set string.
|
495
|
+
#
|
496
|
+
# @return [Hash] The tag set hash.
|
497
|
+
def convert_read_set(name, value)
|
498
|
+
hash_extractor('readPreferenceTags', value)
|
499
|
+
end
|
500
|
+
|
501
|
+
# Converts +value+ as a write concern.
|
502
|
+
#
|
503
|
+
# If +value+ is the word "majority", returns the symbol :majority.
|
504
|
+
# If +value+ is a number, returns the number as an integer.
|
505
|
+
# Otherwise returns the string +value+ unchanged.
|
506
|
+
#
|
507
|
+
# @param [ String ] name Name of the URI option being processed.
|
508
|
+
# @param value [ String ] URI option value.
|
509
|
+
#
|
510
|
+
# @return [ Integer | Symbol | String ] Converted value.
|
511
|
+
def convert_w(name, value)
|
512
|
+
case value
|
513
|
+
when 'majority'
|
514
|
+
:majority
|
515
|
+
when /\A[0-9]+\z/
|
516
|
+
value.to_i
|
517
|
+
else
|
518
|
+
value
|
519
|
+
end
|
520
|
+
end
|
521
|
+
|
522
|
+
def revert_w(value)
|
523
|
+
case value
|
524
|
+
when Symbol
|
525
|
+
value.to_s
|
526
|
+
else
|
527
|
+
value
|
528
|
+
end
|
529
|
+
end
|
530
|
+
|
531
|
+
# Parses the zlib compression level.
|
532
|
+
#
|
533
|
+
# @param [ String ] name Name of the URI option being processed.
|
534
|
+
# @param value [ String ] The zlib compression level string.
|
535
|
+
#
|
536
|
+
# @return [ Integer | nil ] The compression level value if it is between -1 and 9 (inclusive),
|
537
|
+
# otherwise nil (and a warning will be logged).
|
538
|
+
def convert_zlib_compression_level(name, value)
|
539
|
+
if /\A-?\d+\z/ =~ value
|
540
|
+
i = value.to_i
|
541
|
+
|
542
|
+
if i >= -1 && i <= 9
|
543
|
+
return i
|
544
|
+
end
|
545
|
+
end
|
546
|
+
|
547
|
+
log_warn("#{value} is not a valid zlibCompressionLevel")
|
548
|
+
nil
|
549
|
+
end
|
550
|
+
|
551
|
+
def revert_zlib_compression_level(value)
|
552
|
+
value
|
553
|
+
end
|
554
|
+
|
555
|
+
# Extract values from the string and put them into a nested hash.
|
556
|
+
#
|
557
|
+
# @param [ String ] name Name of the URI option being processed.
|
558
|
+
# @param value [ String ] The string to build a hash from.
|
559
|
+
#
|
560
|
+
# @return [ Hash ] The hash built from the string.
|
561
|
+
def hash_extractor(name, value)
|
562
|
+
h = {}
|
563
|
+
value.split(',').each do |tag|
|
564
|
+
k, v = tag.split(':')
|
565
|
+
if v.nil?
|
566
|
+
log_warn("Invalid hash value for #{name}: key `#{k}` does not have a value: #{value}")
|
567
|
+
next
|
568
|
+
end
|
569
|
+
|
570
|
+
h[k.to_sym] = v
|
571
|
+
end
|
572
|
+
if h.empty?
|
573
|
+
nil
|
574
|
+
else
|
575
|
+
h
|
576
|
+
end
|
577
|
+
end
|
578
|
+
|
579
|
+
end
|
580
|
+
|
581
|
+
end
|
582
|
+
end
|