mongo 2.13.0 → 2.15.0.alpha

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.
Files changed (375) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +1 -4
  3. data.tar.gz.sig +0 -0
  4. data/README.md +4 -1
  5. data/Rakefile +46 -18
  6. data/lib/mongo.rb +32 -0
  7. data/lib/mongo/address.rb +1 -1
  8. data/lib/mongo/address/ipv4.rb +1 -1
  9. data/lib/mongo/address/ipv6.rb +1 -1
  10. data/lib/mongo/auth/aws/conversation.rb +1 -4
  11. data/lib/mongo/auth/base.rb +13 -7
  12. data/lib/mongo/auth/conversation_base.rb +32 -0
  13. data/lib/mongo/auth/cr/conversation.rb +6 -29
  14. data/lib/mongo/auth/gssapi/conversation.rb +4 -15
  15. data/lib/mongo/auth/ldap/conversation.rb +3 -14
  16. data/lib/mongo/auth/sasl_conversation_base.rb +1 -13
  17. data/lib/mongo/auth/scram_conversation_base.rb +7 -34
  18. data/lib/mongo/auth/user/view.rb +16 -9
  19. data/lib/mongo/auth/x509/conversation.rb +4 -25
  20. data/lib/mongo/background_thread.rb +11 -0
  21. data/lib/mongo/bulk_write.rb +38 -18
  22. data/lib/mongo/caching_cursor.rb +74 -0
  23. data/lib/mongo/client.rb +142 -16
  24. data/lib/mongo/cluster.rb +22 -31
  25. data/lib/mongo/cluster/reapers/cursor_reaper.rb +6 -2
  26. data/lib/mongo/cluster/sdam_flow.rb +14 -0
  27. data/lib/mongo/cluster/topology/single.rb +1 -1
  28. data/lib/mongo/collection.rb +58 -18
  29. data/lib/mongo/collection/view.rb +24 -20
  30. data/lib/mongo/collection/view/aggregation.rb +26 -5
  31. data/lib/mongo/collection/view/builder/find_command.rb +38 -18
  32. data/lib/mongo/collection/view/change_stream.rb +1 -1
  33. data/lib/mongo/collection/view/explainable.rb +27 -8
  34. data/lib/mongo/collection/view/iterable.rb +73 -13
  35. data/lib/mongo/collection/view/map_reduce.rb +2 -2
  36. data/lib/mongo/collection/view/readable.rb +57 -21
  37. data/lib/mongo/collection/view/writable.rb +29 -15
  38. data/lib/mongo/crypt/encryption_io.rb +6 -6
  39. data/lib/mongo/cursor.rb +18 -5
  40. data/lib/mongo/database.rb +28 -5
  41. data/lib/mongo/database/view.rb +2 -2
  42. data/lib/mongo/error.rb +11 -1
  43. data/lib/mongo/error/bulk_write_error.rb +17 -3
  44. data/lib/mongo/error/internal_driver_error.rb +22 -0
  45. data/lib/mongo/error/invalid_read_concern.rb +28 -0
  46. data/lib/mongo/error/operation_failure.rb +26 -7
  47. data/lib/mongo/error/parser.rb +65 -12
  48. data/lib/mongo/error/server_api_conflict.rb +23 -0
  49. data/lib/mongo/error/server_api_not_supported.rb +24 -0
  50. data/lib/mongo/error/server_certificate_revoked.rb +22 -0
  51. data/lib/mongo/error/unmet_dependency.rb +21 -0
  52. data/lib/mongo/error/unsupported_option.rb +14 -12
  53. data/lib/mongo/grid/fs_bucket.rb +37 -37
  54. data/lib/mongo/index/view.rb +21 -11
  55. data/lib/mongo/lint.rb +2 -1
  56. data/lib/mongo/logger.rb +3 -3
  57. data/lib/mongo/monitoring.rb +13 -4
  58. data/lib/mongo/monitoring/event/server_heartbeat_failed.rb +27 -16
  59. data/lib/mongo/monitoring/event/server_heartbeat_succeeded.rb +26 -15
  60. data/lib/mongo/operation.rb +4 -2
  61. data/lib/mongo/operation/aggregate/result.rb +9 -8
  62. data/lib/mongo/operation/collections_info.rb +18 -1
  63. data/lib/mongo/operation/collections_info/command.rb +5 -0
  64. data/lib/mongo/operation/collections_info/result.rb +18 -1
  65. data/lib/mongo/operation/context.rb +99 -0
  66. data/lib/mongo/operation/delete/bulk_result.rb +2 -0
  67. data/lib/mongo/operation/delete/result.rb +3 -0
  68. data/lib/mongo/operation/explain/command.rb +4 -0
  69. data/lib/mongo/operation/explain/legacy.rb +4 -0
  70. data/lib/mongo/operation/explain/op_msg.rb +6 -0
  71. data/lib/mongo/operation/explain/result.rb +3 -0
  72. data/lib/mongo/operation/find/legacy/result.rb +2 -0
  73. data/lib/mongo/operation/find/result.rb +13 -0
  74. data/lib/mongo/operation/get_more/result.rb +3 -0
  75. data/lib/mongo/operation/indexes.rb +15 -1
  76. data/lib/mongo/operation/indexes/result.rb +5 -0
  77. data/lib/mongo/operation/insert/bulk_result.rb +5 -0
  78. data/lib/mongo/operation/insert/command.rb +2 -2
  79. data/lib/mongo/operation/insert/legacy.rb +2 -2
  80. data/lib/mongo/operation/insert/op_msg.rb +2 -2
  81. data/lib/mongo/operation/insert/result.rb +5 -0
  82. data/lib/mongo/operation/list_collections/result.rb +9 -1
  83. data/lib/mongo/operation/map_reduce/result.rb +10 -0
  84. data/lib/mongo/operation/parallel_scan/result.rb +4 -0
  85. data/lib/mongo/operation/result.rb +37 -6
  86. data/lib/mongo/operation/shared/bypass_document_validation.rb +1 -0
  87. data/lib/mongo/operation/shared/causal_consistency_supported.rb +1 -0
  88. data/lib/mongo/operation/shared/executable.rb +25 -14
  89. data/lib/mongo/operation/shared/executable_no_validate.rb +2 -2
  90. data/lib/mongo/operation/shared/idable.rb +2 -1
  91. data/lib/mongo/operation/shared/limited.rb +1 -0
  92. data/lib/mongo/operation/shared/object_id_generator.rb +1 -0
  93. data/lib/mongo/operation/shared/op_msg_or_command.rb +1 -7
  94. data/lib/mongo/operation/shared/op_msg_or_find_command.rb +1 -7
  95. data/lib/mongo/operation/shared/polymorphic_operation.rb +39 -0
  96. data/lib/mongo/operation/shared/response_handling.rb +23 -23
  97. data/lib/mongo/operation/shared/result/aggregatable.rb +1 -0
  98. data/lib/mongo/operation/shared/sessions_supported.rb +14 -2
  99. data/lib/mongo/operation/shared/specifiable.rb +1 -0
  100. data/lib/mongo/operation/shared/write.rb +9 -18
  101. data/lib/mongo/operation/shared/write_concern_supported.rb +1 -0
  102. data/lib/mongo/operation/update/legacy/result.rb +7 -0
  103. data/lib/mongo/operation/update/result.rb +8 -0
  104. data/lib/mongo/operation/users_info/result.rb +3 -0
  105. data/lib/mongo/protocol/compressed.rb +51 -5
  106. data/lib/mongo/protocol/message.rb +31 -4
  107. data/lib/mongo/protocol/msg.rb +37 -12
  108. data/lib/mongo/protocol/query.rb +36 -0
  109. data/lib/mongo/query_cache.rb +272 -0
  110. data/lib/mongo/retryable.rb +9 -2
  111. data/lib/mongo/server.rb +12 -16
  112. data/lib/mongo/server/app_metadata.rb +52 -18
  113. data/lib/mongo/server/connection.rb +5 -0
  114. data/lib/mongo/server/connection_base.rb +16 -15
  115. data/lib/mongo/server/connection_common.rb +2 -2
  116. data/lib/mongo/server/connection_pool.rb +9 -4
  117. data/lib/mongo/server/description.rb +12 -1
  118. data/lib/mongo/server/description/features.rb +9 -8
  119. data/lib/mongo/server/monitor.rb +21 -2
  120. data/lib/mongo/server/monitor/app_metadata.rb +1 -1
  121. data/lib/mongo/server/monitor/connection.rb +12 -13
  122. data/lib/mongo/server/pending_connection.rb +26 -8
  123. data/lib/mongo/server/push_monitor.rb +12 -2
  124. data/lib/mongo/server_selector/base.rb +5 -1
  125. data/lib/mongo/session.rb +7 -3
  126. data/lib/mongo/session/session_pool.rb +4 -2
  127. data/lib/mongo/socket.rb +35 -8
  128. data/lib/mongo/socket/ocsp_cache.rb +97 -0
  129. data/lib/mongo/socket/ocsp_verifier.rb +368 -0
  130. data/lib/mongo/socket/ssl.rb +53 -24
  131. data/lib/mongo/srv/monitor.rb +7 -24
  132. data/lib/mongo/srv/resolver.rb +14 -10
  133. data/lib/mongo/timeout.rb +2 -0
  134. data/lib/mongo/uri.rb +21 -390
  135. data/lib/mongo/uri/options_mapper.rb +620 -0
  136. data/lib/mongo/uri/srv_protocol.rb +3 -2
  137. data/lib/mongo/utils.rb +27 -1
  138. data/lib/mongo/version.rb +1 -1
  139. data/spec/NOTES.aws-auth.md +12 -7
  140. data/spec/README.md +87 -2
  141. data/spec/integration/auth_spec.rb +25 -15
  142. data/spec/integration/bson_symbol_spec.rb +4 -2
  143. data/spec/integration/bulk_write_error_message_spec.rb +41 -0
  144. data/spec/integration/bulk_write_spec.rb +48 -0
  145. data/spec/integration/change_stream_spec.rb +5 -5
  146. data/spec/integration/client_authentication_options_spec.rb +92 -28
  147. data/spec/integration/client_side_encryption/auto_encryption_bulk_writes_spec.rb +6 -2
  148. data/spec/integration/command_monitoring_spec.rb +2 -2
  149. data/spec/integration/connection_pool_populator_spec.rb +4 -2
  150. data/spec/integration/connection_spec.rb +2 -0
  151. data/spec/integration/cursor_reaping_spec.rb +54 -18
  152. data/spec/integration/docs_examples_spec.rb +8 -1
  153. data/spec/integration/fork_reconnect_spec.rb +60 -2
  154. data/spec/integration/ocsp_connectivity_spec.rb +26 -0
  155. data/spec/integration/ocsp_verifier_cache_spec.rb +188 -0
  156. data/spec/integration/ocsp_verifier_spec.rb +340 -0
  157. data/spec/integration/operation_failure_code_spec.rb +1 -1
  158. data/spec/integration/operation_failure_message_spec.rb +90 -0
  159. data/spec/integration/query_cache_spec.rb +1045 -0
  160. data/spec/integration/query_cache_transactions_spec.rb +190 -0
  161. data/spec/integration/reconnect_spec.rb +1 -1
  162. data/spec/integration/retryable_writes/retryable_writes_40_and_newer_spec.rb +1 -0
  163. data/spec/integration/retryable_writes/shared/performs_legacy_retries.rb +2 -0
  164. data/spec/integration/sdam_error_handling_spec.rb +86 -1
  165. data/spec/integration/sdam_events_spec.rb +8 -7
  166. data/spec/integration/server_selection_spec.rb +36 -0
  167. data/spec/integration/size_limit_spec.rb +20 -19
  168. data/spec/integration/snappy_compression_spec.rb +25 -0
  169. data/spec/integration/srv_monitoring_spec.rb +39 -4
  170. data/spec/integration/srv_spec.rb +56 -0
  171. data/spec/integration/transactions_examples_spec.rb +23 -7
  172. data/spec/integration/zlib_compression_spec.rb +1 -1
  173. data/spec/integration/zstd_compression_spec.rb +26 -0
  174. data/spec/lite_spec_helper.rb +15 -5
  175. data/spec/mongo/address_spec.rb +16 -12
  176. data/spec/mongo/auth/ldap/conversation_spec.rb +1 -1
  177. data/spec/mongo/auth/ldap_spec.rb +5 -1
  178. data/spec/mongo/auth/scram_negotiation_spec.rb +1 -1
  179. data/spec/mongo/auth/scram_spec.rb +1 -1
  180. data/spec/mongo/auth/user_spec.rb +1 -1
  181. data/spec/mongo/auth/x509/conversation_spec.rb +3 -3
  182. data/spec/mongo/bulk_write_spec.rb +2 -2
  183. data/spec/mongo/caching_cursor_spec.rb +70 -0
  184. data/spec/mongo/client_construction_spec.rb +273 -35
  185. data/spec/mongo/client_encryption_spec.rb +16 -10
  186. data/spec/mongo/client_spec.rb +64 -0
  187. data/spec/mongo/cluster/topology/replica_set_spec.rb +1 -1
  188. data/spec/mongo/cluster/topology/sharded_spec.rb +1 -1
  189. data/spec/mongo/cluster/topology/single_spec.rb +15 -6
  190. data/spec/mongo/cluster/topology/unknown_spec.rb +1 -1
  191. data/spec/mongo/cluster/topology_spec.rb +1 -1
  192. data/spec/mongo/cluster_spec.rb +6 -18
  193. data/spec/mongo/collection/view/change_stream_resume_spec.rb +1 -1
  194. data/spec/mongo/collection/view/explainable_spec.rb +87 -4
  195. data/spec/mongo/collection/view/map_reduce_spec.rb +2 -0
  196. data/spec/mongo/collection/view/readable_spec.rb +50 -0
  197. data/spec/mongo/collection_crud_spec.rb +4357 -0
  198. data/spec/mongo/collection_ddl_spec.rb +534 -0
  199. data/spec/mongo/collection_spec.rb +5 -4787
  200. data/spec/mongo/crypt/auto_decryption_context_spec.rb +1 -1
  201. data/spec/mongo/crypt/auto_encryption_context_spec.rb +1 -1
  202. data/spec/mongo/crypt/binary_spec.rb +1 -6
  203. data/spec/mongo/crypt/binding/binary_spec.rb +1 -6
  204. data/spec/mongo/crypt/binding/context_spec.rb +2 -7
  205. data/spec/mongo/crypt/binding/helpers_spec.rb +1 -6
  206. data/spec/mongo/crypt/binding/mongocrypt_spec.rb +2 -7
  207. data/spec/mongo/crypt/binding/status_spec.rb +1 -6
  208. data/spec/mongo/crypt/binding/version_spec.rb +1 -6
  209. data/spec/mongo/crypt/data_key_context_spec.rb +1 -1
  210. data/spec/mongo/crypt/explicit_decryption_context_spec.rb +1 -1
  211. data/spec/mongo/crypt/explicit_encryption_context_spec.rb +1 -1
  212. data/spec/mongo/crypt/status_spec.rb +1 -6
  213. data/spec/mongo/database_spec.rb +174 -4
  214. data/spec/mongo/error/bulk_write_error_spec.rb +3 -3
  215. data/spec/mongo/error/no_server_available_spec.rb +1 -1
  216. data/spec/mongo/error/parser_spec.rb +37 -6
  217. data/spec/mongo/index/view_spec.rb +8 -2
  218. data/spec/mongo/logger_spec.rb +13 -11
  219. data/spec/mongo/monitoring/event/server_closed_spec.rb +1 -1
  220. data/spec/mongo/monitoring/event/server_heartbeat_failed_spec.rb +1 -1
  221. data/spec/mongo/monitoring/event/server_heartbeat_succeeded_spec.rb +1 -1
  222. data/spec/mongo/monitoring/event/server_opening_spec.rb +1 -1
  223. data/spec/mongo/monitoring/event/topology_changed_spec.rb +1 -1
  224. data/spec/mongo/monitoring/event/topology_closed_spec.rb +1 -1
  225. data/spec/mongo/monitoring/event/topology_opening_spec.rb +1 -1
  226. data/spec/mongo/operation/aggregate_spec.rb +2 -1
  227. data/spec/mongo/operation/collections_info_spec.rb +4 -1
  228. data/spec/mongo/operation/command_spec.rb +6 -3
  229. data/spec/mongo/operation/create_index_spec.rb +6 -3
  230. data/spec/mongo/operation/create_user_spec.rb +6 -3
  231. data/spec/mongo/operation/delete/bulk_spec.rb +9 -6
  232. data/spec/mongo/operation/delete/op_msg_spec.rb +3 -3
  233. data/spec/mongo/operation/delete_spec.rb +11 -7
  234. data/spec/mongo/operation/drop_index_spec.rb +6 -2
  235. data/spec/mongo/operation/find/legacy_spec.rb +3 -1
  236. data/spec/mongo/operation/get_more_spec.rb +3 -1
  237. data/spec/mongo/operation/indexes_spec.rb +5 -1
  238. data/spec/mongo/operation/insert/bulk_spec.rb +10 -7
  239. data/spec/mongo/operation/insert/command_spec.rb +2 -2
  240. data/spec/mongo/operation/insert/op_msg_spec.rb +3 -3
  241. data/spec/mongo/operation/insert_spec.rb +15 -12
  242. data/spec/mongo/operation/map_reduce_spec.rb +5 -2
  243. data/spec/mongo/operation/read_preference_op_msg_spec.rb +1 -1
  244. data/spec/mongo/operation/remove_user_spec.rb +6 -3
  245. data/spec/mongo/operation/result_spec.rb +1 -1
  246. data/spec/mongo/operation/update/bulk_spec.rb +9 -6
  247. data/spec/mongo/operation/update/command_spec.rb +2 -2
  248. data/spec/mongo/operation/update/op_msg_spec.rb +3 -3
  249. data/spec/mongo/operation/update_spec.rb +10 -7
  250. data/spec/mongo/operation/update_user_spec.rb +4 -1
  251. data/spec/mongo/protocol/compressed_spec.rb +26 -12
  252. data/spec/mongo/query_cache_middleware_spec.rb +55 -0
  253. data/spec/mongo/query_cache_spec.rb +280 -0
  254. data/spec/mongo/retryable_spec.rb +3 -2
  255. data/spec/mongo/server/app_metadata_shared.rb +2 -2
  256. data/spec/mongo/server/app_metadata_spec.rb +2 -0
  257. data/spec/mongo/server/connection_pool/populator_spec.rb +3 -1
  258. data/spec/mongo/server/connection_pool_spec.rb +8 -4
  259. data/spec/mongo/server/connection_spec.rb +39 -25
  260. data/spec/mongo/server/description_spec.rb +18 -0
  261. data/spec/mongo/server/monitor/connection_spec.rb +17 -7
  262. data/spec/mongo/server/monitor_spec.rb +9 -1
  263. data/spec/mongo/server_selector_spec.rb +2 -2
  264. data/spec/mongo/server_spec.rb +15 -2
  265. data/spec/mongo/socket/ssl_spec.rb +44 -4
  266. data/spec/mongo/socket_spec.rb +2 -2
  267. data/spec/mongo/tls_context_hooks_spec.rb +37 -0
  268. data/spec/mongo/uri/srv_protocol_spec.rb +64 -33
  269. data/spec/mongo/uri_option_parsing_spec.rb +11 -11
  270. data/spec/mongo/uri_spec.rb +68 -41
  271. data/spec/mongo/utils_spec.rb +39 -0
  272. data/spec/runners/auth.rb +3 -0
  273. data/spec/runners/change_streams/test.rb +1 -1
  274. data/spec/runners/connection_string.rb +31 -124
  275. data/spec/runners/crud/requirement.rb +40 -3
  276. data/spec/runners/crud/test_base.rb +0 -19
  277. data/spec/runners/crud/verifier.rb +8 -0
  278. data/spec/runners/server_selection.rb +1 -1
  279. data/spec/runners/transactions/operation.rb +13 -2
  280. data/spec/runners/transactions/test.rb +3 -2
  281. data/spec/runners/unified.rb +96 -0
  282. data/spec/runners/unified/assertions.rb +249 -0
  283. data/spec/runners/unified/change_stream_operations.rb +26 -0
  284. data/spec/runners/unified/crud_operations.rb +199 -0
  285. data/spec/runners/unified/ddl_operations.rb +96 -0
  286. data/spec/runners/unified/entity_map.rb +39 -0
  287. data/spec/runners/unified/error.rb +25 -0
  288. data/spec/runners/unified/event_subscriber.rb +91 -0
  289. data/spec/runners/unified/exceptions.rb +21 -0
  290. data/spec/runners/unified/grid_fs_operations.rb +55 -0
  291. data/spec/runners/unified/support_operations.rb +250 -0
  292. data/spec/runners/unified/test.rb +393 -0
  293. data/spec/runners/unified/test_group.rb +28 -0
  294. data/spec/runners/unified/using_hash.rb +31 -0
  295. data/spec/shared/LICENSE +20 -0
  296. data/spec/shared/bin/get-mongodb-download-url +17 -0
  297. data/spec/shared/lib/mrss/child_process_helper.rb +80 -0
  298. data/spec/shared/lib/mrss/cluster_config.rb +218 -0
  299. data/spec/shared/lib/mrss/constraints.rb +346 -0
  300. data/spec/shared/lib/mrss/docker_runner.rb +262 -0
  301. data/spec/shared/lib/mrss/lite_constraints.rb +175 -0
  302. data/spec/shared/lib/mrss/server_version_registry.rb +112 -0
  303. data/spec/shared/lib/mrss/spec_organizer.rb +149 -0
  304. data/spec/shared/lib/mrss/utils.rb +15 -0
  305. data/spec/shared/share/Dockerfile.erb +231 -0
  306. data/spec/shared/shlib/distro.sh +73 -0
  307. data/spec/shared/shlib/server.sh +290 -0
  308. data/spec/shared/shlib/set_env.sh +128 -0
  309. data/spec/solo/clean_exit_spec.rb +21 -0
  310. data/spec/spec_helper.rb +7 -2
  311. data/spec/spec_tests/cmap_spec.rb +7 -3
  312. data/spec/spec_tests/crud_unified_spec.rb +10 -0
  313. data/spec/spec_tests/data/change_streams/change-streams-errors.yml +0 -1
  314. data/spec/spec_tests/data/change_streams/change-streams.yml +0 -2
  315. data/spec/spec_tests/data/cmap/pool-checkout-connection.yml +6 -2
  316. data/spec/spec_tests/data/cmap/pool-create-min-size.yml +3 -0
  317. data/spec/spec_tests/data/connection_string/valid-warnings.yml +24 -0
  318. data/spec/spec_tests/data/crud_unified/estimatedDocumentCount.yml +267 -0
  319. data/spec/spec_tests/data/retryable_reads/estimatedDocumentCount-4.9.yml +60 -0
  320. data/spec/spec_tests/data/retryable_reads/{estimatedDocumentCount.yml → estimatedDocumentCount-pre4.9.yml} +2 -0
  321. data/spec/spec_tests/data/retryable_reads/estimatedDocumentCount-serverErrors-4.9.yml +146 -0
  322. data/spec/spec_tests/data/retryable_reads/{estimatedDocumentCount-serverErrors.yml → estimatedDocumentCount-serverErrors-pre4.9.yml} +2 -0
  323. data/spec/spec_tests/data/retryable_reads/listIndexNames.yml +1 -1
  324. data/spec/spec_tests/data/sdam_monitoring/discovered_standalone.yml +1 -3
  325. data/spec/spec_tests/data/sdam_monitoring/standalone.yml +2 -2
  326. data/spec/spec_tests/data/sdam_monitoring/standalone_repeated.yml +2 -2
  327. data/spec/spec_tests/data/sdam_monitoring/standalone_suppress_equal_description_changes.yml +2 -2
  328. data/spec/spec_tests/data/sdam_monitoring/standalone_to_rs_with_me_mismatch.yml +2 -2
  329. data/spec/spec_tests/data/unified/valid-fail/operation-failure.yml +31 -0
  330. data/spec/spec_tests/data/unified/valid-pass/poc-change-streams.yml +220 -0
  331. data/spec/spec_tests/data/unified/valid-pass/poc-command-monitoring.yml +102 -0
  332. data/spec/spec_tests/data/unified/valid-pass/poc-crud.yml +184 -0
  333. data/spec/spec_tests/data/unified/valid-pass/poc-gridfs.yml +155 -0
  334. data/spec/spec_tests/data/unified/valid-pass/poc-retryable-reads.yml +193 -0
  335. data/spec/spec_tests/data/unified/valid-pass/poc-retryable-writes.yml +210 -0
  336. data/spec/spec_tests/data/unified/valid-pass/poc-sessions.yml +215 -0
  337. data/spec/spec_tests/data/unified/valid-pass/poc-transactions-convenient-api.yml +235 -0
  338. data/spec/spec_tests/data/unified/valid-pass/poc-transactions-mongos-pin-auto.yml +169 -0
  339. data/spec/spec_tests/data/unified/valid-pass/poc-transactions.yml +170 -0
  340. data/spec/spec_tests/data/uri_options/auth-options.yml +25 -0
  341. data/spec/spec_tests/data/uri_options/compression-options.yml +7 -4
  342. data/spec/spec_tests/data/uri_options/read-preference-options.yml +24 -0
  343. data/spec/spec_tests/data/uri_options/ruby-connection-options.yml +1 -0
  344. data/spec/spec_tests/data/uri_options/tls-options.yml +160 -4
  345. data/spec/spec_tests/data/versioned_api/crud-api-version-1-strict.yml +416 -0
  346. data/spec/spec_tests/data/versioned_api/crud-api-version-1.yml +409 -0
  347. data/spec/spec_tests/data/versioned_api/runcommand-helper-no-api-version-declared.yml +67 -0
  348. data/spec/spec_tests/data/versioned_api/test-commands-deprecation-errors.yml +47 -0
  349. data/spec/spec_tests/data/versioned_api/test-commands-strict-mode.yml +44 -0
  350. data/spec/spec_tests/data/versioned_api/transaction-handling.yml +180 -0
  351. data/spec/spec_tests/dns_seedlist_discovery_spec.rb +9 -1
  352. data/spec/spec_tests/unified_spec.rb +15 -0
  353. data/spec/spec_tests/uri_options_spec.rb +47 -33
  354. data/spec/spec_tests/versioned_api_spec.rb +10 -0
  355. data/spec/stress/fork_reconnect_stress_spec.rb +1 -1
  356. data/spec/support/certificates/atlas-ocsp-ca.crt +28 -0
  357. data/spec/support/certificates/atlas-ocsp.crt +41 -0
  358. data/spec/support/client_registry_macros.rb +11 -2
  359. data/spec/support/common_shortcuts.rb +59 -0
  360. data/spec/support/constraints.rb +6 -253
  361. data/spec/support/matchers.rb +16 -0
  362. data/spec/support/ocsp +1 -0
  363. data/spec/support/session_registry.rb +52 -0
  364. data/spec/support/shared/session.rb +2 -2
  365. data/spec/support/spec_config.rb +68 -3
  366. data/spec/support/spec_setup.rb +48 -38
  367. data/spec/support/utils.rb +102 -4
  368. metadata +1087 -936
  369. metadata.gz.sig +0 -0
  370. data/lib/mongo/operation/shared/collections_info_or_list_collections.rb +0 -56
  371. data/lib/mongo/operation/shared/op_msg_or_list_indexes_command.rb +0 -47
  372. data/spec/support/child_process_helper.rb +0 -78
  373. data/spec/support/cluster_config.rb +0 -207
  374. data/spec/support/lite_constraints.rb +0 -141
  375. data/spec/support/spec_organizer.rb +0 -129
@@ -0,0 +1,620 @@
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
+ def smc_to_ruby(opts)
86
+ uri_options = {}
87
+
88
+ opts.each do |key, value|
89
+ strategy = URI_OPTION_MAP[key.downcase]
90
+ if strategy.nil?
91
+ log_warn("Unsupported URI option '#{key}' on URI '#{@string}'. It will be ignored.")
92
+ return
93
+ end
94
+
95
+ group = strategy[:group]
96
+ target = if group
97
+ uri_options[group] || {}
98
+ else
99
+ uri_options
100
+ end
101
+
102
+ if key == 'readConcernLevel'
103
+ value = value.to_sym
104
+ end
105
+
106
+ #value = apply_transform(key, value, strategy[:type])
107
+ # Sometimes the value here would be nil, for example if we are processing
108
+ # read preference tags or auth mechanism properties and all of the
109
+ # data within is invalid. Ignore such options.
110
+ unless value.nil?
111
+ merge_uri_option(target, value, strategy[:name])
112
+ end
113
+
114
+ if group && !target.empty? && !uri_options.key?(group)
115
+ uri_options[group] = target
116
+ end
117
+ end
118
+
119
+ #p uri_options
120
+ uri_options
121
+ end
122
+
123
+ # Converts Ruby options provided to "standardized MongoClient options".
124
+ #
125
+ # @param [ Hash ] opts Ruby options to convert.
126
+ #
127
+ # @return [ Hash ] Standardized MongoClient options.
128
+ def ruby_to_smc(opts)
129
+ rv = {}
130
+ URI_OPTION_MAP.each do |uri_key, spec|
131
+ if spec[:group]
132
+ v = opts[spec[:group]]
133
+ v = v && v[spec[:name]]
134
+ else
135
+ v = opts[spec[:name]]
136
+ end
137
+ unless v.nil?
138
+ if spec[:type]
139
+ v = send("revert_#{spec[:type]}", v)
140
+ end
141
+ canonical_key = URI_OPTION_CANONICAL_NAMES[uri_key]
142
+ unless canonical_key
143
+ raise ArgumentError, "Option #{uri_key} is not known"
144
+ end
145
+ rv[canonical_key] = v
146
+ end
147
+ end
148
+ # For options that default to true, remove the value if it is true.
149
+ %w(retryReads retryWrites).each do |k|
150
+ if rv[k]
151
+ rv.delete(k)
152
+ end
153
+ end
154
+ # Remove auth source when it is $external for mechanisms that default
155
+ # (or require) that auth source.
156
+ if %w(MONGODB-AWS).include?(rv['authMechanism']) && rv['authSource'] == '$external'
157
+ rv.delete('authSource')
158
+ end
159
+ # ssl and tls are aliases, remove ssl ones
160
+ rv.delete('ssl')
161
+ # TODO remove authSource if it is the same as the database,
162
+ # requires this method to know the database specified in the client.
163
+ rv
164
+ end
165
+
166
+ private
167
+
168
+ # Applies URI value transformation by either using the default cast
169
+ # or a transformation appropriate for the given type.
170
+ #
171
+ # @param key [String] URI option name.
172
+ # @param value [String] The value to be transformed.
173
+ # @param type [Symbol] The transform method.
174
+ def apply_transform(key, value, type)
175
+ if type
176
+ send("convert_#{type}", key, value)
177
+ else
178
+ value
179
+ end
180
+ end
181
+
182
+ # Merges a new option into the target.
183
+ #
184
+ # If the option exists at the target destination the merge will
185
+ # be an addition.
186
+ #
187
+ # Specifically required to append an additional tag set
188
+ # to the array of tag sets without overwriting the original.
189
+ #
190
+ # @param target [Hash] The destination.
191
+ # @param value [Object] The value to be merged.
192
+ # @param name [Symbol] The name of the option.
193
+ def merge_uri_option(target, value, name)
194
+ if target.key?(name)
195
+ if REPEATABLE_OPTIONS.include?(name)
196
+ target[name] += value
197
+ else
198
+ log_warn("Repeated option key: #{name}.")
199
+ end
200
+ else
201
+ target.merge!(name => value)
202
+ end
203
+ end
204
+
205
+ # Hash for storing map of URI option parameters to conversion strategies
206
+ URI_OPTION_MAP = {}
207
+
208
+ # @return [ Hash<String, String> ] Map from lowercased to canonical URI
209
+ # option names.
210
+ URI_OPTION_CANONICAL_NAMES = {}
211
+
212
+ # Simple internal dsl to register a MongoDB URI option in the URI_OPTION_MAP.
213
+ #
214
+ # @param uri_key [String] The MongoDB URI option to register.
215
+ # @param name [Symbol] The name of the option in the driver.
216
+ # @param extra [Hash] Extra options.
217
+ # * :group [Symbol] Nested hash where option will go.
218
+ # * :type [Symbol] Name of function to transform value.
219
+ def self.uri_option(uri_key, name, **extra)
220
+ URI_OPTION_MAP[uri_key.downcase] = { name: name }.update(extra)
221
+ URI_OPTION_CANONICAL_NAMES[uri_key.downcase] = uri_key
222
+ end
223
+
224
+ # Replica Set Options
225
+ uri_option 'replicaSet', :replica_set
226
+
227
+ # Timeout Options
228
+ uri_option 'connectTimeoutMS', :connect_timeout, type: :ms
229
+ uri_option 'socketTimeoutMS', :socket_timeout, type: :ms
230
+ uri_option 'serverSelectionTimeoutMS', :server_selection_timeout, type: :ms
231
+ uri_option 'localThresholdMS', :local_threshold, type: :ms
232
+ uri_option 'heartbeatFrequencyMS', :heartbeat_frequency, type: :ms
233
+ uri_option 'maxIdleTimeMS', :max_idle_time, type: :ms
234
+
235
+ # Write Options
236
+ uri_option 'w', :w, group: :write_concern, type: :w
237
+ uri_option 'journal', :j, group: :write_concern, type: :bool
238
+ uri_option 'fsync', :fsync, group: :write_concern, type: :bool
239
+ uri_option 'wTimeoutMS', :wtimeout, group: :write_concern, type: :integer
240
+
241
+ # Read Options
242
+ uri_option 'readPreference', :mode, group: :read, type: :read_mode
243
+ uri_option 'readPreferenceTags', :tag_sets, group: :read, type: :read_tags
244
+ uri_option 'maxStalenessSeconds', :max_staleness, group: :read, type: :max_staleness
245
+
246
+ # Pool options
247
+ uri_option 'minPoolSize', :min_pool_size, type: :integer
248
+ uri_option 'maxPoolSize', :max_pool_size, type: :integer
249
+ uri_option 'waitQueueTimeoutMS', :wait_queue_timeout, type: :ms
250
+
251
+ # Security Options
252
+ uri_option 'ssl', :ssl, type: :repeated_bool
253
+ uri_option 'tls', :ssl, type: :repeated_bool
254
+ uri_option 'tlsAllowInvalidCertificates', :ssl_verify_certificate,
255
+ type: :inverse_bool
256
+ uri_option 'tlsAllowInvalidHostnames', :ssl_verify_hostname,
257
+ type: :inverse_bool
258
+ uri_option 'tlsCAFile', :ssl_ca_cert
259
+ uri_option 'tlsCertificateKeyFile', :ssl_cert
260
+ uri_option 'tlsCertificateKeyFilePassword', :ssl_key_pass_phrase
261
+ uri_option 'tlsInsecure', :ssl_verify, type: :inverse_bool
262
+ uri_option 'tlsDisableOCSPEndpointCheck', :ssl_verify_ocsp_endpoint,
263
+ type: :inverse_bool
264
+
265
+ # Topology options
266
+ uri_option 'directConnection', :direct_connection, type: :bool
267
+ uri_option 'connect', :connect, type: :symbol
268
+
269
+ # Auth Options
270
+ uri_option 'authSource', :auth_source
271
+ uri_option 'authMechanism', :auth_mech, type: :auth_mech
272
+ uri_option 'authMechanismProperties', :auth_mech_properties, type: :auth_mech_props
273
+
274
+ # Client Options
275
+ uri_option 'appName', :app_name
276
+ uri_option 'compressors', :compressors, type: :array
277
+ uri_option 'readConcernLevel', :level, group: :read_concern, type: :symbol
278
+ uri_option 'retryReads', :retry_reads, type: :bool
279
+ uri_option 'retryWrites', :retry_writes, type: :bool
280
+ uri_option 'zlibCompressionLevel', :zlib_compression_level, type: :zlib_compression_level
281
+
282
+ # Converts +value+ to a boolean.
283
+ #
284
+ # Returns true for 'true', false for 'false', otherwise nil.
285
+ #
286
+ # @param [ String ] name Name of the URI option being processed.
287
+ # @param value [ String ] URI option value.
288
+ #
289
+ # @return [ true | false | nil ] Converted value.
290
+ def convert_bool(name, value)
291
+ case value
292
+ when "true", 'TRUE'
293
+ true
294
+ when "false", 'FALSE'
295
+ false
296
+ else
297
+ log_warn("invalid boolean option for #{name}: #{value}")
298
+ nil
299
+ end
300
+ end
301
+
302
+ def revert_bool(value)
303
+ value
304
+ end
305
+
306
+ # Converts the value into a boolean and returns it wrapped in an array.
307
+ #
308
+ # @param name [ String ] Name of the URI option being processed.
309
+ # @param value [ String ] URI option value.
310
+ #
311
+ # @return [ Array<true | false> ] The boolean value parsed and wraped
312
+ # in an array.
313
+ def convert_repeated_bool(name, value)
314
+ [convert_bool(name, value)]
315
+ end
316
+
317
+ def revert_repeated_bool(value)
318
+ value
319
+ end
320
+
321
+ # Parses a boolean value and returns its inverse.
322
+ #
323
+ # @param [ String ] name Name of the URI option being processed.
324
+ # @param value [ String ] The URI option value.
325
+ #
326
+ # @return [ true | false | nil ] The inverse of the boolean value parsed out, otherwise nil
327
+ # (and a warning will be logged).
328
+ def convert_inverse_bool(name, value)
329
+ b = convert_bool(name, value)
330
+
331
+ if b.nil?
332
+ nil
333
+ else
334
+ !b
335
+ end
336
+ end
337
+
338
+ def revert_inverse_bool(value)
339
+ !value
340
+ end
341
+
342
+ # Converts +value+ into an integer.
343
+ #
344
+ # If the value is not a valid integer, warns and returns nil.
345
+ #
346
+ # @param [ String ] name Name of the URI option being processed.
347
+ # @param value [ String ] URI option value.
348
+ #
349
+ # @return [ nil | Integer ] Converted value.
350
+ def convert_integer(name, value)
351
+ unless /\A\d+\z/ =~ value
352
+ log_warn("#{value} is not a valid integer for #{name}")
353
+ return nil
354
+ end
355
+
356
+ value.to_i
357
+ end
358
+
359
+ def revert_integer(value)
360
+ value
361
+ end
362
+
363
+ # Ruby's convention is to provide timeouts in seconds, not milliseconds and
364
+ # to use fractions where more precision is necessary. The connection string
365
+ # options are always in MS so we provide an easy conversion type.
366
+ #
367
+ # @param [ String ] name Name of the URI option being processed.
368
+ # @param [ Integer ] value The millisecond value.
369
+ #
370
+ # @return [ Float ] The seconds value.
371
+ #
372
+ # @since 2.0.0
373
+ def convert_ms(name, value)
374
+ unless /\A-?\d+(\.\d+)?\z/ =~ value
375
+ log_warn("Invalid ms value for #{name}: #{value}")
376
+ return nil
377
+ end
378
+
379
+ if value[0] == '-'
380
+ log_warn("#{name} cannot be a negative number")
381
+ return nil
382
+ end
383
+
384
+ value.to_f / 1000
385
+ end
386
+
387
+ def revert_ms(value)
388
+ (value * 1000).round
389
+ end
390
+
391
+ # Converts +value+ into a symbol.
392
+ #
393
+ # @param [ String ] name Name of the URI option being processed.
394
+ # @param value [ String ] URI option value.
395
+ #
396
+ # @return [ Symbol ] Converted value.
397
+ def convert_symbol(name, value)
398
+ value.to_sym
399
+ end
400
+
401
+ def revert_symbol(value)
402
+ value.to_s
403
+ end
404
+
405
+ # Extract values from the string and put them into an array.
406
+ #
407
+ # @param [ String ] name Name of the URI option being processed.
408
+ # @param [ String ] value The string to build an array from.
409
+ #
410
+ # @return [ Array ] The array built from the string.
411
+ def convert_array(name, value)
412
+ value.split(',')
413
+ end
414
+
415
+ def revert_array(value)
416
+ value
417
+ end
418
+
419
+ # Authentication mechanism transformation.
420
+ #
421
+ # @param [ String ] name Name of the URI option being processed.
422
+ # @param value [String] The authentication mechanism.
423
+ #
424
+ # @return [Symbol] The transformed authentication mechanism.
425
+ def convert_auth_mech(name, value)
426
+ (AUTH_MECH_MAP[value.upcase] || value).tap do |mech|
427
+ log_warn("#{value} is not a valid auth mechanism") unless mech
428
+ end
429
+ end
430
+
431
+ def revert_auth_mech(value)
432
+ found = AUTH_MECH_MAP.detect do |k, v|
433
+ v == value
434
+ end
435
+ if found
436
+ found.first
437
+ else
438
+ raise ArgumentError, "Unknown auth mechanism #{value}"
439
+ end
440
+ end
441
+
442
+ # Auth mechanism properties extractor.
443
+ #
444
+ # @param [ String ] name Name of the URI option being processed.
445
+ # @param value [ String ] The auth mechanism properties string.
446
+ #
447
+ # @return [ Hash ] The auth mechanism properties hash.
448
+ def convert_auth_mech_props(name, value)
449
+ properties = hash_extractor('authMechanismProperties', value)
450
+ if properties
451
+ properties.each do |k, v|
452
+ if k.to_s.downcase == 'canonicalize_host_name' && v
453
+ properties[k] = (v.downcase == 'true')
454
+ end
455
+ end
456
+ end
457
+ properties
458
+ end
459
+
460
+ def revert_auth_mech_props(value)
461
+ value
462
+ end
463
+
464
+ # Parses the max staleness value, which must be either "0" or an integer
465
+ # greater or equal to 90.
466
+ #
467
+ # @param [ String ] name Name of the URI option being processed.
468
+ # @param value [ String ] The max staleness string.
469
+ #
470
+ # @return [ Integer | nil ] The max staleness integer parsed out if it is valid, otherwise nil
471
+ # (and a warning will be logged).
472
+ def convert_max_staleness(name, value)
473
+ if /\A-?\d+\z/ =~ value
474
+ int = value.to_i
475
+
476
+ if int == -1
477
+ int = nil
478
+ end
479
+
480
+ if int && (int >= 0 && int < 90 || int < 0)
481
+ log_warn("max staleness should be either 0 or greater than 90: #{value}")
482
+ int = nil
483
+ end
484
+
485
+ return int
486
+ end
487
+
488
+ log_warn("Invalid max staleness value: #{value}")
489
+ nil
490
+ end
491
+
492
+ def revert_max_staleness(value)
493
+ value
494
+ end
495
+
496
+ # Read preference mode transformation.
497
+ #
498
+ # @param [ String ] name Name of the URI option being processed.
499
+ # @param value [String] The read mode string value.
500
+ #
501
+ # @return [Symbol] The read mode symbol.
502
+ def convert_read_mode(name, value)
503
+ READ_MODE_MAP[value.downcase] || value
504
+ end
505
+
506
+ def revert_read_mode(value)
507
+ value.to_s.gsub(/_(\w)/) { $1.upcase }
508
+ end
509
+
510
+ # Read preference tags transformation.
511
+ #
512
+ # @param [ String ] name Name of the URI option being processed.
513
+ # @param value [String] The string representing tag set.
514
+ #
515
+ # @return [Array<Hash>] Array with tag set.
516
+ def convert_read_tags(name, value)
517
+ converted = convert_read_set(name, value)
518
+ if converted
519
+ [converted]
520
+ else
521
+ nil
522
+ end
523
+ end
524
+
525
+ def revert_read_tags(value)
526
+ value
527
+ end
528
+
529
+ # Read preference tag set extractor.
530
+ #
531
+ # @param [ String ] name Name of the URI option being processed.
532
+ # @param value [String] The tag set string.
533
+ #
534
+ # @return [Hash] The tag set hash.
535
+ def convert_read_set(name, value)
536
+ hash_extractor('readPreferenceTags', value)
537
+ end
538
+
539
+ # Converts +value+ as a write concern.
540
+ #
541
+ # If +value+ is the word "majority", returns the symbol :majority.
542
+ # If +value+ is a number, returns the number as an integer.
543
+ # Otherwise returns the string +value+ unchanged.
544
+ #
545
+ # @param [ String ] name Name of the URI option being processed.
546
+ # @param value [ String ] URI option value.
547
+ #
548
+ # @return [ Integer | Symbol | String ] Converted value.
549
+ def convert_w(name, value)
550
+ case value
551
+ when 'majority'
552
+ :majority
553
+ when /\A[0-9]+\z/
554
+ value.to_i
555
+ else
556
+ value
557
+ end
558
+ end
559
+
560
+ def revert_w(value)
561
+ case value
562
+ when Symbol
563
+ value.to_s
564
+ else
565
+ value
566
+ end
567
+ end
568
+
569
+ # Parses the zlib compression level.
570
+ #
571
+ # @param [ String ] name Name of the URI option being processed.
572
+ # @param value [ String ] The zlib compression level string.
573
+ #
574
+ # @return [ Integer | nil ] The compression level value if it is between -1 and 9 (inclusive),
575
+ # otherwise nil (and a warning will be logged).
576
+ def convert_zlib_compression_level(name, value)
577
+ if /\A-?\d+\z/ =~ value
578
+ i = value.to_i
579
+
580
+ if i >= -1 && i <= 9
581
+ return i
582
+ end
583
+ end
584
+
585
+ log_warn("#{value} is not a valid zlibCompressionLevel")
586
+ nil
587
+ end
588
+
589
+ def revert_zlib_compression_level(value)
590
+ value
591
+ end
592
+
593
+ # Extract values from the string and put them into a nested hash.
594
+ #
595
+ # @param [ String ] name Name of the URI option being processed.
596
+ # @param value [ String ] The string to build a hash from.
597
+ #
598
+ # @return [ Hash ] The hash built from the string.
599
+ def hash_extractor(name, value)
600
+ h = {}
601
+ value.split(',').each do |tag|
602
+ k, v = tag.split(':')
603
+ if v.nil?
604
+ log_warn("Invalid hash value for #{name}: key `#{k}` does not have a value: #{value}")
605
+ next
606
+ end
607
+
608
+ h[k.to_sym] = v
609
+ end
610
+ if h.empty?
611
+ nil
612
+ else
613
+ h
614
+ end
615
+ end
616
+
617
+ end
618
+
619
+ end
620
+ end