mongo 2.5.3 → 2.6.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.
Files changed (394) hide show
  1. checksums.yaml +5 -5
  2. checksums.yaml.gz.sig +0 -0
  3. data.tar.gz.sig +0 -0
  4. data/LICENSE +1 -1
  5. data/README.md +3 -2
  6. data/lib/mongo.rb +2 -2
  7. data/lib/mongo/address.rb +10 -2
  8. data/lib/mongo/address/ipv4.rb +1 -1
  9. data/lib/mongo/address/ipv6.rb +26 -5
  10. data/lib/mongo/address/unix.rb +1 -1
  11. data/lib/mongo/auth.rb +10 -3
  12. data/lib/mongo/auth/cr.rb +4 -1
  13. data/lib/mongo/auth/cr/conversation.rb +4 -1
  14. data/lib/mongo/auth/ldap.rb +1 -1
  15. data/lib/mongo/auth/ldap/conversation.rb +1 -1
  16. data/lib/mongo/auth/roles.rb +1 -1
  17. data/lib/mongo/auth/scram.rb +24 -7
  18. data/lib/mongo/auth/scram/conversation.rb +52 -19
  19. data/lib/mongo/auth/stringprep.rb +114 -0
  20. data/lib/mongo/auth/stringprep/profiles/sasl.rb +73 -0
  21. data/lib/mongo/auth/stringprep/tables.rb +3232 -0
  22. data/lib/mongo/auth/stringprep/unicode_normalize/normalize.rb +174 -0
  23. data/lib/mongo/auth/stringprep/unicode_normalize/tables.rb +1170 -0
  24. data/lib/mongo/auth/user.rb +14 -3
  25. data/lib/mongo/auth/user/view.rb +1 -1
  26. data/lib/mongo/auth/x509.rb +1 -1
  27. data/lib/mongo/auth/x509/conversation.rb +1 -1
  28. data/lib/mongo/bson.rb +1 -1
  29. data/lib/mongo/bulk_write.rb +8 -8
  30. data/lib/mongo/bulk_write/combineable.rb +1 -1
  31. data/lib/mongo/bulk_write/ordered_combiner.rb +1 -1
  32. data/lib/mongo/bulk_write/result.rb +1 -1
  33. data/lib/mongo/bulk_write/result_combiner.rb +4 -4
  34. data/lib/mongo/bulk_write/transformable.rb +1 -1
  35. data/lib/mongo/bulk_write/unordered_combiner.rb +1 -1
  36. data/lib/mongo/bulk_write/validatable.rb +1 -1
  37. data/lib/mongo/client.rb +115 -24
  38. data/lib/mongo/cluster.rb +17 -10
  39. data/lib/mongo/cluster/app_metadata.rb +7 -1
  40. data/lib/mongo/cluster/periodic_executor.rb +1 -1
  41. data/lib/mongo/cluster/reapers/socket_reaper.rb +1 -1
  42. data/lib/mongo/cluster/topology.rb +12 -2
  43. data/lib/mongo/cluster/topology/replica_set.rb +9 -1
  44. data/lib/mongo/cluster/topology/sharded.rb +1 -1
  45. data/lib/mongo/cluster/topology/single.rb +1 -1
  46. data/lib/mongo/cluster/topology/unknown.rb +1 -1
  47. data/lib/mongo/collection.rb +75 -19
  48. data/lib/mongo/collection/view.rb +1 -1
  49. data/lib/mongo/collection/view/aggregation.rb +1 -1
  50. data/lib/mongo/collection/view/builder.rb +1 -1
  51. data/lib/mongo/collection/view/builder/aggregation.rb +3 -3
  52. data/lib/mongo/collection/view/builder/find_command.rb +1 -1
  53. data/lib/mongo/collection/view/builder/flags.rb +1 -1
  54. data/lib/mongo/collection/view/builder/map_reduce.rb +1 -1
  55. data/lib/mongo/collection/view/builder/modifiers.rb +1 -1
  56. data/lib/mongo/collection/view/builder/op_query.rb +1 -1
  57. data/lib/mongo/collection/view/change_stream.rb +193 -17
  58. data/lib/mongo/collection/view/change_stream/retryable.rb +3 -20
  59. data/lib/mongo/collection/view/explainable.rb +1 -1
  60. data/lib/mongo/collection/view/immutable.rb +1 -1
  61. data/lib/mongo/collection/view/iterable.rb +2 -2
  62. data/lib/mongo/collection/view/map_reduce.rb +1 -1
  63. data/lib/mongo/collection/view/readable.rb +108 -29
  64. data/lib/mongo/collection/view/writable.rb +3 -3
  65. data/lib/mongo/cursor.rb +44 -4
  66. data/lib/mongo/cursor/builder.rb +1 -1
  67. data/lib/mongo/cursor/builder/get_more_command.rb +1 -1
  68. data/lib/mongo/cursor/builder/kill_cursors_command.rb +1 -1
  69. data/lib/mongo/cursor/builder/op_get_more.rb +1 -1
  70. data/lib/mongo/cursor/builder/op_kill_cursors.rb +1 -1
  71. data/lib/mongo/database.rb +46 -3
  72. data/lib/mongo/database/view.rb +11 -11
  73. data/lib/mongo/dbref.rb +1 -1
  74. data/lib/mongo/error.rb +57 -1
  75. data/lib/mongo/error/bulk_write_error.rb +2 -2
  76. data/lib/mongo/error/change_stream_resumable.rb +37 -0
  77. data/lib/mongo/error/closed_stream.rb +1 -1
  78. data/lib/mongo/error/extra_file_chunk.rb +1 -1
  79. data/lib/mongo/error/failed_stringprep_validation.rb +38 -0
  80. data/lib/mongo/error/file_not_found.rb +1 -1
  81. data/lib/mongo/error/insufficient_iteration_count.rb +38 -0
  82. data/lib/mongo/error/invalid_application_name.rb +1 -1
  83. data/lib/mongo/error/invalid_bulk_operation.rb +1 -1
  84. data/lib/mongo/error/invalid_bulk_operation_type.rb +1 -1
  85. data/lib/mongo/error/invalid_collection_name.rb +1 -1
  86. data/lib/mongo/error/invalid_database_name.rb +1 -1
  87. data/lib/mongo/error/invalid_document.rb +1 -1
  88. data/lib/mongo/error/invalid_file.rb +1 -1
  89. data/lib/mongo/error/invalid_file_revision.rb +1 -1
  90. data/lib/mongo/error/invalid_min_pool_size.rb +1 -1
  91. data/lib/mongo/error/invalid_nonce.rb +1 -1
  92. data/lib/mongo/error/invalid_read_option.rb +35 -0
  93. data/lib/mongo/error/invalid_replacement_document.rb +1 -1
  94. data/lib/mongo/error/invalid_server_preference.rb +1 -1
  95. data/lib/mongo/error/invalid_session.rb +1 -1
  96. data/lib/mongo/error/invalid_signature.rb +1 -1
  97. data/lib/mongo/error/invalid_transaction_operation.rb +82 -0
  98. data/lib/mongo/error/invalid_txt_record.rb +1 -1
  99. data/lib/mongo/error/invalid_update_document.rb +1 -1
  100. data/lib/mongo/error/invalid_uri.rb +1 -1
  101. data/lib/mongo/error/invalid_write_concern.rb +1 -1
  102. data/lib/mongo/error/max_bson_size.rb +1 -1
  103. data/lib/mongo/error/max_message_size.rb +1 -1
  104. data/lib/mongo/error/mismatched_domain.rb +1 -1
  105. data/lib/mongo/error/missing_file_chunk.rb +1 -1
  106. data/lib/mongo/error/missing_resume_token.rb +1 -1
  107. data/lib/mongo/error/multi_index_drop.rb +1 -1
  108. data/lib/mongo/error/need_primary_server.rb +1 -1
  109. data/lib/mongo/error/no_server_available.rb +1 -1
  110. data/lib/mongo/error/no_srv_records.rb +1 -1
  111. data/lib/mongo/error/operation_failure.rb +108 -14
  112. data/lib/mongo/error/parser.rb +50 -1
  113. data/lib/mongo/error/socket_error.rb +5 -2
  114. data/lib/mongo/error/socket_timeout_error.rb +5 -2
  115. data/lib/mongo/error/unchangeable_collection_option.rb +1 -1
  116. data/lib/mongo/error/unexpected_chunk_length.rb +1 -1
  117. data/lib/mongo/error/unexpected_response.rb +1 -1
  118. data/lib/mongo/error/unknown_payload_type.rb +1 -1
  119. data/lib/mongo/error/unsupported_array_filters.rb +1 -1
  120. data/lib/mongo/error/unsupported_collation.rb +1 -1
  121. data/lib/mongo/error/unsupported_features.rb +1 -1
  122. data/lib/mongo/error/unsupported_message_type.rb +1 -1
  123. data/lib/mongo/error/write_retryable.rb +27 -0
  124. data/lib/mongo/event.rb +10 -9
  125. data/lib/mongo/event/base.rb +33 -0
  126. data/lib/mongo/event/description_changed.rb +2 -2
  127. data/lib/mongo/event/listeners.rb +1 -1
  128. data/lib/mongo/event/member_discovered.rb +4 -2
  129. data/lib/mongo/event/primary_elected.rb +2 -2
  130. data/lib/mongo/event/publisher.rb +1 -1
  131. data/lib/mongo/event/standalone_discovered.rb +2 -2
  132. data/lib/mongo/event/subscriber.rb +1 -1
  133. data/lib/mongo/grid.rb +1 -1
  134. data/lib/mongo/grid/file.rb +1 -1
  135. data/lib/mongo/grid/file/chunk.rb +3 -3
  136. data/lib/mongo/grid/file/info.rb +26 -3
  137. data/lib/mongo/grid/fs_bucket.rb +1 -1
  138. data/lib/mongo/grid/stream.rb +1 -1
  139. data/lib/mongo/grid/stream/read.rb +1 -1
  140. data/lib/mongo/grid/stream/write.rb +1 -1
  141. data/lib/mongo/index.rb +1 -1
  142. data/lib/mongo/index/view.rb +1 -1
  143. data/lib/mongo/loggable.rb +1 -1
  144. data/lib/mongo/logger.rb +1 -1
  145. data/lib/mongo/monitoring.rb +99 -62
  146. data/lib/mongo/monitoring/command_log_subscriber.rb +2 -2
  147. data/lib/mongo/monitoring/event.rb +2 -1
  148. data/lib/mongo/monitoring/event/command_failed.rb +19 -6
  149. data/lib/mongo/monitoring/event/command_started.rb +14 -3
  150. data/lib/mongo/monitoring/event/command_succeeded.rb +5 -3
  151. data/lib/mongo/monitoring/event/secure.rb +1 -1
  152. data/lib/mongo/monitoring/event/server_closed.rb +2 -2
  153. data/lib/mongo/monitoring/event/server_description_changed.rb +2 -2
  154. data/lib/mongo/monitoring/event/server_opening.rb +11 -2
  155. data/lib/mongo/monitoring/event/topology_changed.rb +13 -2
  156. data/lib/mongo/monitoring/event/topology_closed.rb +2 -2
  157. data/lib/mongo/monitoring/event/topology_opening.rb +11 -2
  158. data/lib/mongo/monitoring/publishable.rb +10 -6
  159. data/lib/mongo/monitoring/sdam_log_subscriber.rb +1 -1
  160. data/lib/mongo/monitoring/server_closed_log_subscriber.rb +1 -1
  161. data/lib/mongo/monitoring/server_description_changed_log_subscriber.rb +1 -1
  162. data/lib/mongo/monitoring/server_opening_log_subscriber.rb +1 -1
  163. data/lib/mongo/monitoring/topology_changed_log_subscriber.rb +1 -1
  164. data/lib/mongo/monitoring/topology_opening_log_subscriber.rb +1 -1
  165. data/lib/mongo/operation/aggregate/op_msg.rb +3 -0
  166. data/lib/mongo/operation/create/op_msg.rb +9 -0
  167. data/lib/mongo/operation/create_index/op_msg.rb +9 -0
  168. data/lib/mongo/operation/create_user/command.rb +1 -1
  169. data/lib/mongo/operation/create_user/op_msg.rb +10 -1
  170. data/lib/mongo/operation/delete/op_msg.rb +3 -0
  171. data/lib/mongo/operation/distinct/op_msg.rb +9 -0
  172. data/lib/mongo/operation/drop/op_msg.rb +9 -0
  173. data/lib/mongo/operation/drop_database/op_msg.rb +9 -0
  174. data/lib/mongo/operation/drop_index/op_msg.rb +9 -0
  175. data/lib/mongo/operation/explain/op_msg.rb +3 -0
  176. data/lib/mongo/operation/find/op_msg.rb +3 -0
  177. data/lib/mongo/operation/get_more.rb +1 -1
  178. data/lib/mongo/operation/get_more/command.rb +1 -1
  179. data/lib/mongo/operation/get_more/legacy.rb +1 -1
  180. data/lib/mongo/operation/get_more/op_msg.rb +3 -0
  181. data/lib/mongo/operation/indexes/op_msg.rb +3 -0
  182. data/lib/mongo/operation/indexes/result.rb +1 -1
  183. data/lib/mongo/operation/insert/bulk_result.rb +32 -2
  184. data/lib/mongo/operation/insert/op_msg.rb +3 -0
  185. data/lib/mongo/operation/insert/result.rb +1 -1
  186. data/lib/mongo/operation/kill_cursors/op_msg.rb +9 -0
  187. data/lib/mongo/operation/list_collections/op_msg.rb +3 -0
  188. data/lib/mongo/operation/list_collections/result.rb +5 -1
  189. data/lib/mongo/operation/map_reduce/op_msg.rb +3 -0
  190. data/lib/mongo/operation/map_reduce/result.rb +1 -1
  191. data/lib/mongo/operation/parallel_scan/op_msg.rb +3 -0
  192. data/lib/mongo/operation/remove_user/op_msg.rb +9 -0
  193. data/lib/mongo/operation/result.rb +27 -14
  194. data/lib/mongo/operation/shared/executable.rb +1 -0
  195. data/lib/mongo/operation/shared/sessions_supported.rb +78 -7
  196. data/lib/mongo/operation/shared/specifiable.rb +18 -2
  197. data/lib/mongo/operation/shared/write_concern_supported.rb +1 -1
  198. data/lib/mongo/operation/update/op_msg.rb +3 -0
  199. data/lib/mongo/operation/update_user/command.rb +1 -1
  200. data/lib/mongo/operation/update_user/op_msg.rb +10 -1
  201. data/lib/mongo/operation/users_info/op_msg.rb +3 -0
  202. data/lib/mongo/options.rb +1 -1
  203. data/lib/mongo/options/mapper.rb +1 -1
  204. data/lib/mongo/options/redacted.rb +1 -1
  205. data/lib/mongo/protocol/bit_vector.rb +1 -1
  206. data/lib/mongo/protocol/compressed.rb +1 -1
  207. data/lib/mongo/protocol/delete.rb +1 -1
  208. data/lib/mongo/protocol/get_more.rb +7 -7
  209. data/lib/mongo/protocol/insert.rb +1 -1
  210. data/lib/mongo/protocol/kill_cursors.rb +1 -1
  211. data/lib/mongo/protocol/message.rb +5 -5
  212. data/lib/mongo/protocol/msg.rb +9 -7
  213. data/lib/mongo/protocol/query.rb +1 -1
  214. data/lib/mongo/protocol/registry.rb +1 -1
  215. data/lib/mongo/protocol/reply.rb +10 -10
  216. data/lib/mongo/protocol/serializers.rb +1 -1
  217. data/lib/mongo/protocol/update.rb +1 -1
  218. data/lib/mongo/retryable.rb +22 -14
  219. data/lib/mongo/server.rb +1 -1
  220. data/lib/mongo/server/connectable.rb +1 -1
  221. data/lib/mongo/server/connection.rb +16 -4
  222. data/lib/mongo/server/connection_pool.rb +1 -1
  223. data/lib/mongo/server/connection_pool/queue.rb +1 -1
  224. data/lib/mongo/server/context.rb +1 -1
  225. data/lib/mongo/server/description.rb +14 -2
  226. data/lib/mongo/server/description/features.rb +10 -9
  227. data/lib/mongo/server/description/inspector.rb +1 -1
  228. data/lib/mongo/server/description/inspector/description_changed.rb +1 -1
  229. data/lib/mongo/server/description/inspector/member_discovered.rb +1 -1
  230. data/lib/mongo/server/description/inspector/primary_elected.rb +1 -1
  231. data/lib/mongo/server/description/inspector/standalone_discovered.rb +1 -1
  232. data/lib/mongo/server/monitor.rb +15 -3
  233. data/lib/mongo/server/monitor/connection.rb +1 -1
  234. data/lib/mongo/server_selector.rb +1 -1
  235. data/lib/mongo/server_selector/nearest.rb +1 -1
  236. data/lib/mongo/server_selector/primary.rb +1 -1
  237. data/lib/mongo/server_selector/primary_preferred.rb +1 -1
  238. data/lib/mongo/server_selector/secondary.rb +1 -1
  239. data/lib/mongo/server_selector/secondary_preferred.rb +1 -1
  240. data/lib/mongo/server_selector/selectable.rb +7 -2
  241. data/lib/mongo/session.rb +389 -12
  242. data/lib/mongo/session/server_session.rb +7 -2
  243. data/lib/mongo/session/session_pool.rb +1 -1
  244. data/lib/mongo/socket.rb +1 -1
  245. data/lib/mongo/socket/ssl.rb +1 -1
  246. data/lib/mongo/socket/tcp.rb +1 -1
  247. data/lib/mongo/socket/unix.rb +1 -1
  248. data/lib/mongo/uri.rb +6 -4
  249. data/lib/mongo/uri/srv_protocol.rb +1 -1
  250. data/lib/mongo/version.rb +2 -2
  251. data/lib/mongo/write_concern.rb +1 -1
  252. data/lib/mongo/write_concern/acknowledged.rb +1 -1
  253. data/lib/mongo/write_concern/normalizable.rb +1 -1
  254. data/lib/mongo/write_concern/unacknowledged.rb +1 -1
  255. data/mongo.gemspec +4 -1
  256. data/spec/atlas/atlas_connectivity_spec.rb +54 -0
  257. data/spec/integration/bulk_insert_spec.rb +78 -0
  258. data/spec/integration/change_stream_spec.rb +365 -0
  259. data/spec/integration/command_monitoring_spec.rb +92 -0
  260. data/spec/lite_spec_helper.rb +63 -0
  261. data/spec/mongo/address/ipv6_spec.rb +29 -1
  262. data/spec/mongo/address_spec.rb +34 -0
  263. data/spec/mongo/auth/scram/conversation_spec.rb +326 -120
  264. data/spec/mongo/auth/scram/negotiation_spec.rb +574 -0
  265. data/spec/mongo/auth/scram_spec.rb +107 -38
  266. data/spec/mongo/auth/stringprep/profiles/sasl_spec.rb +113 -0
  267. data/spec/mongo/auth/stringprep_spec.rb +188 -0
  268. data/spec/mongo/auth/user/view_spec.rb +5 -2
  269. data/spec/mongo/auth/user_spec.rb +1 -1
  270. data/spec/mongo/bulk_write/result_spec.rb +120 -0
  271. data/spec/mongo/bulk_write_spec.rb +42 -2
  272. data/spec/mongo/client_spec.rb +121 -9
  273. data/spec/mongo/cluster/app_metadata_spec.rb +14 -1
  274. data/spec/mongo/cluster/topology_spec.rb +1 -23
  275. data/spec/mongo/collection/view/change_stream_spec.rb +62 -180
  276. data/spec/mongo/collection_spec.rb +45 -12
  277. data/spec/mongo/cursor/builder/get_more_command_spec.rb +7 -7
  278. data/spec/mongo/cursor_spec.rb +2 -2
  279. data/spec/mongo/database_spec.rb +3 -3
  280. data/spec/mongo/docs_examples_spec.rb +194 -0
  281. data/spec/mongo/error/operation_failure_spec.rb +152 -0
  282. data/spec/mongo/error/parser_spec.rb +127 -0
  283. data/spec/mongo/grid/fs_bucket_spec.rb +32 -0
  284. data/spec/mongo/grid/stream/write_spec.rb +40 -1
  285. data/spec/mongo/monitoring/event/command_failed_spec.rb +30 -0
  286. data/spec/mongo/monitoring/event/command_started_spec.rb +26 -4
  287. data/spec/mongo/monitoring/event/command_succeeded_spec.rb +29 -7
  288. data/spec/mongo/monitoring_spec.rb +28 -3
  289. data/spec/mongo/protocol/get_more_spec.rb +2 -2
  290. data/spec/mongo/retryable_spec.rb +252 -34
  291. data/spec/mongo/retryable_writes_spec.rb +468 -544
  292. data/spec/mongo/server/connection_spec.rb +5 -5
  293. data/spec/mongo/server/description_spec.rb +23 -6
  294. data/spec/mongo/session/server_session_spec.rb +2 -2
  295. data/spec/mongo/session/session_pool_spec.rb +2 -2
  296. data/spec/mongo/transactions_examples_spec.rb +227 -0
  297. data/spec/mongo/transactions_spec.rb +44 -0
  298. data/spec/spec_helper.rb +135 -49
  299. data/spec/spec_tests/change_streams_spec.rb +42 -0
  300. data/spec/{mongo → spec_tests}/command_monitoring_spec.rb +8 -2
  301. data/spec/{mongo → spec_tests}/connection_string_spec.rb +1 -1
  302. data/spec/{mongo → spec_tests}/crud_spec.rb +5 -7
  303. data/spec/{mongo → spec_tests}/dns_seedlist_discovery_spec.rb +1 -1
  304. data/spec/{mongo → spec_tests}/gridfs_spec.rb +0 -0
  305. data/spec/{mongo → spec_tests}/max_staleness_spec.rb +0 -0
  306. data/spec/spec_tests/retryable_writes_spec.rb +78 -0
  307. data/spec/{mongo → spec_tests}/sdam_monitoring_spec.rb +4 -3
  308. data/spec/{mongo → spec_tests}/sdam_spec.rb +7 -7
  309. data/spec/{mongo → spec_tests}/server_selection_rtt_spec.rb +0 -0
  310. data/spec/{mongo → spec_tests}/server_selection_spec.rb +0 -0
  311. data/spec/support/authorization.rb +18 -6
  312. data/spec/support/change_streams.rb +265 -0
  313. data/spec/support/change_streams/operation.rb +62 -0
  314. data/spec/support/change_streams_tests/change-streams-errors.yml +53 -0
  315. data/spec/support/change_streams_tests/change-streams.yml +299 -0
  316. data/spec/support/command_monitoring.rb +1 -1
  317. data/spec/support/command_monitoring/bulkWrite.yml +4 -28
  318. data/spec/support/command_monitoring/command.yml +19 -0
  319. data/spec/support/command_monitoring/find.yml +17 -19
  320. data/spec/support/command_monitoring/insertMany.yml +2 -8
  321. data/spec/support/command_monitoring/unacknowledgedBulkWrite.yml +34 -0
  322. data/spec/support/connection_string.rb +1 -1
  323. data/spec/support/constraints.rb +56 -0
  324. data/spec/support/crud.rb +9 -4
  325. data/spec/support/crud/read.rb +24 -3
  326. data/spec/support/crud/write.rb +7 -2
  327. data/spec/support/crud_tests/read/count-collation.yml +12 -2
  328. data/spec/support/crud_tests/read/count.yml +43 -5
  329. data/spec/support/gridfs.rb +1 -1
  330. data/spec/support/primary_socket.rb +21 -0
  331. data/spec/support/retryable_writes_tests/bulkWrite-serverErrors.yml +90 -0
  332. data/spec/support/retryable_writes_tests/bulkWrite.yml +99 -1
  333. data/spec/support/retryable_writes_tests/deleteOne-serverErrors.yml +50 -0
  334. data/spec/support/retryable_writes_tests/deleteOne.yml +10 -1
  335. data/spec/support/retryable_writes_tests/findOneAndDelete-serverErrors.yml +50 -0
  336. data/spec/support/retryable_writes_tests/findOneAndDelete.yml +39 -30
  337. data/spec/support/retryable_writes_tests/findOneAndReplace-serverErrors.yml +54 -0
  338. data/spec/support/retryable_writes_tests/findOneAndReplace.yml +9 -0
  339. data/spec/support/retryable_writes_tests/findOneAndUpdate-serverErrors.yml +54 -0
  340. data/spec/support/retryable_writes_tests/findOneAndUpdate.yml +9 -0
  341. data/spec/support/retryable_writes_tests/insertMany-serverErrors.yml +59 -0
  342. data/spec/support/retryable_writes_tests/insertMany.yml +11 -6
  343. data/spec/support/retryable_writes_tests/insertOne-serverErrors.yml +471 -0
  344. data/spec/support/retryable_writes_tests/insertOne.yml +9 -0
  345. data/spec/support/retryable_writes_tests/replaceOne-serverErrors.yml +58 -0
  346. data/spec/support/retryable_writes_tests/replaceOne.yml +9 -0
  347. data/spec/support/retryable_writes_tests/updateOne-serverErrors.yml +58 -0
  348. data/spec/support/retryable_writes_tests/updateOne.yml +71 -53
  349. data/spec/support/sdam/rs/normalize_case_me.yml +100 -0
  350. data/spec/support/sdam/sharded/compatible.yml +38 -0
  351. data/spec/support/sdam/sharded/mongos_disconnect.yml +9 -3
  352. data/spec/support/sdam/sharded/multiple_mongoses.yml +6 -2
  353. data/spec/support/sdam/sharded/non_mongos_removed.yml +6 -2
  354. data/spec/support/sdam/sharded/too_new.yml +36 -0
  355. data/spec/support/sdam/sharded/too_old.yml +36 -0
  356. data/spec/support/sdam/single/compatible.yml +26 -0
  357. data/spec/support/sdam/single/direct_connection_external_ip.yml +3 -1
  358. data/spec/support/sdam/single/direct_connection_mongos.yml +3 -1
  359. data/spec/support/sdam/single/direct_connection_rsarbiter.yml +3 -1
  360. data/spec/support/sdam/single/direct_connection_rsprimary.yml +3 -1
  361. data/spec/support/sdam/single/direct_connection_rssecondary.yml +3 -1
  362. data/spec/support/sdam/single/direct_connection_slave.yml +3 -1
  363. data/spec/support/sdam/single/direct_connection_standalone.yml +3 -1
  364. data/spec/support/sdam/single/not_ok_response.yml +6 -2
  365. data/spec/support/sdam/single/standalone_removed.yml +3 -1
  366. data/spec/support/sdam/single/too_new.yml +26 -0
  367. data/spec/support/sdam/single/too_old.yml +24 -0
  368. data/spec/support/shared/session.rb +107 -0
  369. data/spec/support/transactions.rb +391 -0
  370. data/spec/support/transactions/operation.rb +373 -0
  371. data/spec/support/transactions_tests/abort.yml +403 -0
  372. data/spec/support/transactions_tests/bulk.yml +267 -0
  373. data/spec/support/transactions_tests/causal-consistency.yml +173 -0
  374. data/spec/support/transactions_tests/commit.yml +593 -0
  375. data/spec/support/transactions_tests/delete.yml +184 -0
  376. data/spec/support/transactions_tests/error-labels.yml +948 -0
  377. data/spec/support/transactions_tests/errors.yml +125 -0
  378. data/spec/support/transactions_tests/findOneAndDelete.yml +126 -0
  379. data/spec/support/transactions_tests/findOneAndReplace.yml +140 -0
  380. data/spec/support/transactions_tests/findOneAndUpdate.yml +228 -0
  381. data/spec/support/transactions_tests/insert.yml +264 -0
  382. data/spec/support/transactions_tests/isolation.yml +125 -0
  383. data/spec/support/transactions_tests/read-pref.yml +340 -0
  384. data/spec/support/transactions_tests/reads.yml +298 -0
  385. data/spec/support/transactions_tests/retryable-abort.yml +1292 -0
  386. data/spec/support/transactions_tests/retryable-commit.yml +1332 -0
  387. data/spec/support/transactions_tests/retryable-writes.yml +208 -0
  388. data/spec/support/transactions_tests/run-command.yml +189 -0
  389. data/spec/support/transactions_tests/transaction-options.yml +877 -0
  390. data/spec/support/transactions_tests/update.yml +246 -0
  391. data/spec/support/transactions_tests/write-concern.yml +236 -0
  392. metadata +494 -359
  393. metadata.gz.sig +0 -0
  394. data/lib/csasl/csasl.bundle +0 -0
@@ -1,4 +1,4 @@
1
- # Copyright (C) 2014-2017 MongoDB, Inc.
1
+ # Copyright (C) 2014-2018 MongoDB, Inc.
2
2
  #
3
3
  # Licensed under the Apache License, Version 2.0 (the "License");
4
4
  # you may not use this file except in compliance with the License.
@@ -1,4 +1,4 @@
1
- # Copyright (C) 2015 MongoDB, Inc.
1
+ # Copyright (C) 2015-2018 MongoDB, Inc.
2
2
  #
3
3
  # Licensed under the Apache License, Version 2.0 (the "License");
4
4
  # you may not use this file except in compliance with the License.
@@ -30,6 +30,7 @@ module Mongo
30
30
  #
31
31
  # @note This only retries read operations on socket errors.
32
32
  #
33
+ # @param [ Mongo::Session ] session The session that the operation is being run on.
33
34
  # @param [ Proc ] block The block to execute.
34
35
  #
35
36
  # @yieldparam [ Server ] server The server to which the write should be sent.
@@ -37,18 +38,18 @@ module Mongo
37
38
  # @return [ Result ] The result of the operation.
38
39
  #
39
40
  # @since 2.1.0
40
- def read_with_retry
41
+ def read_with_retry(session = nil)
41
42
  attempt = 0
42
43
  begin
43
44
  attempt += 1
44
45
  yield
45
46
  rescue Error::SocketError, Error::SocketTimeoutError => e
46
- raise(e) if attempt > cluster.max_read_retries
47
+ raise(e) if attempt > cluster.max_read_retries || (session && session.in_transaction?)
47
48
  log_retry(e)
48
49
  cluster.scan!
49
50
  retry
50
51
  rescue Error::OperationFailure => e
51
- if cluster.sharded? && e.retryable?
52
+ if cluster.sharded? && e.retryable? && !(session && session.in_transaction?)
52
53
  raise(e) if attempt > cluster.max_read_retries
53
54
  log_retry(e)
54
55
  sleep(cluster.read_retry_interval)
@@ -93,28 +94,32 @@ module Mongo
93
94
  # @note This only retries operations on not master failures, since it is
94
95
  # the only case we can be sure a partial write did not already occur.
95
96
  #
97
+ # @param [ true | false ] ending_transaction True if the write operation is abortTransaction or
98
+ # commitTransaction, false otherwise.
96
99
  # @param [ Proc ] block The block to execute.
97
100
  #
98
101
  # @return [ Result ] The result of the operation.
99
102
  #
100
103
  # @since 2.1.0
101
- def write_with_retry(session, write_concern, &block)
102
- unless retry_write_allowed?(session, write_concern)
103
- return legacy_write_with_retry(&block)
104
+ def write_with_retry(session, write_concern, ending_transaction = false, &block)
105
+ unless retry_write_allowed?(session, write_concern) || ending_transaction
106
+ return legacy_write_with_retry(nil, session, &block)
104
107
  end
105
108
 
106
109
  server = cluster.next_primary
107
- unless server.retry_writes?
108
- return legacy_write_with_retry(server, &block)
110
+
111
+ unless server.retry_writes? || ending_transaction
112
+ return legacy_write_with_retry(server, session, &block)
109
113
  end
110
114
 
111
115
  begin
112
- txn_num = session.next_txn_num
116
+ txn_num = session.in_transaction? ? session.txn_num : session.next_txn_num
113
117
  yield(server, txn_num)
114
118
  rescue Error::SocketError, Error::SocketTimeoutError => e
119
+ raise e if session.in_transaction? && !ending_transaction
115
120
  retry_write(e, txn_num, &block)
116
121
  rescue Error::OperationFailure => e
117
- raise e unless e.write_retryable?
122
+ raise e if (session.in_transaction? && !ending_transaction) || !e.write_retryable?
118
123
  retry_write(e, txn_num, &block)
119
124
  end
120
125
  end
@@ -123,7 +128,7 @@ module Mongo
123
128
 
124
129
  def retry_write_allowed?(session, write_concern)
125
130
  session && session.retry_writes? &&
126
- (write_concern.nil? || write_concern.acknowledged?)
131
+ (write_concern.nil? || write_concern.acknowledged?) or false
127
132
  end
128
133
 
129
134
  def retry_write(original_error, txn_num, &block)
@@ -143,7 +148,10 @@ module Mongo
143
148
  raise original_error
144
149
  end
145
150
 
146
- def legacy_write_with_retry(server = nil)
151
+ def legacy_write_with_retry(server = nil, session = nil)
152
+ # This is the pre-session retry logic, and is not subject to
153
+ # current retryable write specifications.
154
+ # In particular it does not retry on SocketError and SocketTimeoutError.
147
155
  attempt = 0
148
156
  begin
149
157
  attempt += 1
@@ -151,7 +159,7 @@ module Mongo
151
159
  rescue Error::OperationFailure => e
152
160
  server = nil
153
161
  raise(e) if attempt > Cluster::MAX_WRITE_RETRIES
154
- if e.write_retryable?
162
+ if e.write_retryable? && !(session && session.in_transaction?)
155
163
  log_retry(e)
156
164
  cluster.scan!
157
165
  retry
@@ -1,4 +1,4 @@
1
- # Copyright (C) 2014-2017 MongoDB, Inc.
1
+ # Copyright (C) 2014-2018 MongoDB, Inc.
2
2
  #
3
3
  # Licensed under the Apache License, Version 2.0 (the "License");
4
4
  # you may not use this file except in compliance with the License.
@@ -1,4 +1,4 @@
1
- # Copyright (C) 2015-2017 MongoDB, Inc.
1
+ # Copyright (C) 2015-2018 MongoDB, Inc.
2
2
 
3
3
  # Licensed under the Apache License, Version 2.0 (the "License");
4
4
  # you may not use this file except in compliance with the License.
@@ -1,4 +1,4 @@
1
- # Copyright (C) 2014-2017 MongoDB, Inc.
1
+ # Copyright (C) 2014-2018 MongoDB, Inc.
2
2
 
3
3
  # Licensed under the Apache License, Version 2.0 (the "License");
4
4
  # you may not use this file except in compliance with the License.
@@ -135,7 +135,7 @@ module Mongo
135
135
  # @param [ Array<Message> ] messages The messages to dispatch.
136
136
  # @param [ Integer ] operation_id The operation id to link messages.
137
137
  #
138
- # @return [ Protocol::Message ] The reply if needed.
138
+ # @return [ Protocol::Message | nil ] The reply if needed.
139
139
  #
140
140
  # @since 2.0.0
141
141
  def dispatch(messages, operation_id = nil)
@@ -237,14 +237,26 @@ module Mongo
237
237
  min_wire_version = response[Description::MIN_WIRE_VERSION] || Description::LEGACY_WIRE_VERSION
238
238
  max_wire_version = response[Description::MAX_WIRE_VERSION] || Description::LEGACY_WIRE_VERSION
239
239
  features = Description::Features.new(min_wire_version..max_wire_version)
240
- @auth_mechanism = (features.scram_sha_1_enabled? || @server.features.scram_sha_1_enabled?) ? :scram : :mongodb_cr
240
+
241
+ if response["ok"] == 1
242
+ @auth_mechanism = if response['saslSupportedMechs']
243
+ if response['saslSupportedMechs'].include?(Mongo::Auth::SCRAM::SCRAM_SHA_256_MECHANISM)
244
+ :scram256
245
+ else
246
+ :scram
247
+ end
248
+ elsif features.scram_sha_1_enabled? || @server.features.scram_sha_1_enabled?
249
+ :scram
250
+ else
251
+ :mongodb_cr
252
+ end
253
+ end
241
254
  end
242
255
  end
243
256
 
244
257
  def authenticate!
245
258
  if options[:user] || options[:auth_mech]
246
259
  user = Auth::User.new(Options::Redacted.new(:auth_mech => default_mechanism, :client_key => @client_key).merge(options))
247
-
248
260
  @server.handle_auth_failure! do
249
261
  reply = Auth.get(user).login(self)
250
262
  @client_key ||= user.send(:client_key) if user.mechanism == :scram
@@ -1,4 +1,4 @@
1
- # Copyright (C) 2014-2017 MongoDB, Inc.
1
+ # Copyright (C) 2014-2018 MongoDB, Inc.
2
2
  #
3
3
  # Licensed under the Apache License, Version 2.0 (the "License");
4
4
  # you may not use this file except in compliance with the License.
@@ -1,4 +1,4 @@
1
- # Copyright (C) 2014-2017 MongoDB, Inc.
1
+ # Copyright (C) 2014-2018 MongoDB, Inc.
2
2
  #
3
3
  # Licensed under the Apache License, Version 2.0 (the "License");
4
4
  # you may not use this file except in compliance with the License.
@@ -1,4 +1,4 @@
1
- # Copyright (C) 2014-2017 MongoDB, Inc.
1
+ # Copyright (C) 2014-2018 MongoDB, Inc.
2
2
  #
3
3
  # Licensed under the Apache License, Version 2.0 (the 'License');
4
4
  # you may not use this file except in compliance with the License.
@@ -1,4 +1,4 @@
1
- # Copyright (C) 2014-2017 MongoDB, Inc.
1
+ # Copyright (C) 2014-2018 MongoDB, Inc.
2
2
  #
3
3
  # Licensed under the Apache License, Version 2.0 (the 'License');
4
4
  # you may not use this file except in compliance with the License.
@@ -461,6 +461,18 @@ module Mongo
461
461
  @passives ||= (config[PASSIVES] || []).map { |s| s.downcase }
462
462
  end
463
463
 
464
+ # Get the address of the primary host.
465
+ #
466
+ # @example Get the address of the primary.
467
+ # description.primary_host
468
+ #
469
+ # @return [ String | nil ] The address of the primary.
470
+ #
471
+ # @since 2.6.0
472
+ def primary_host
473
+ config[PRIMARY_HOST] && config[PRIMARY_HOST].downcase
474
+ end
475
+
464
476
  # Will return true if the server is a primary.
465
477
  #
466
478
  # @example Is the server a primary?
@@ -471,7 +483,7 @@ module Mongo
471
483
  # @since 2.0.0
472
484
  def primary?
473
485
  !!config[PRIMARY] &&
474
- (config[PRIMARY_HOST].nil? || config[PRIMARY_HOST] == address.to_s) &&
486
+ (primary_host.nil? || primary_host == address.to_s) &&
475
487
  !replica_set_name.nil?
476
488
  end
477
489
 
@@ -1,4 +1,4 @@
1
- # Copyright (C) 2014 MongoDB, Inc.
1
+ # Copyright (C) 2014-2018 MongoDB, Inc.
2
2
  #
3
3
  # Licensed under the Apache License, Version 2.0 (the 'License');
4
4
  # you may not use this file except in compliance with the License.
@@ -25,6 +25,8 @@ module Mongo
25
25
  #
26
26
  # @since 2.0.0
27
27
  MAPPINGS = {
28
+ :transactions => 7,
29
+ :scram_sha_256 => 7,
28
30
  :array_filters => 6,
29
31
  :op_msg => 6,
30
32
  :sessions => 6,
@@ -53,19 +55,18 @@ module Mongo
53
55
  # The wire protocol versions that this version of the driver supports.
54
56
  #
55
57
  # @since 2.0.0
56
- DRIVER_WIRE_VERSIONS = (2..6).freeze
58
+ DRIVER_WIRE_VERSIONS = (2..7).freeze
57
59
 
58
60
  # Create the methods for each mapping to tell if they are supported.
59
61
  #
60
62
  # @since 2.0.0
61
63
  MAPPINGS.each do |name, version|
62
-
63
- # Determine whether or not the feature is enabled.
64
+ # Determine whether or not the feature is supported.
64
65
  #
65
66
  # @example Is a feature enabled?
66
67
  # features.list_collections_enabled?
67
68
  #
68
- # @return [ true, false ] If the feature is enabled.
69
+ # @return [ true, false ] Whether the feature is supported.
69
70
  #
70
71
  # @since 2.0.0
71
72
  define_method("#{name}_enabled?") do
@@ -91,14 +92,14 @@ module Mongo
91
92
  @address = address
92
93
  end
93
94
 
94
- # Check that there is an overlap between the driver supported wire version range
95
- # and the server wire version range.
95
+ # Check that there is an overlap between the driver supported wire
96
+ # version range and the server wire version range.
96
97
  #
97
98
  # @example Verify the wire version overlap.
98
99
  # features.check_driver_support!
99
100
  #
100
- # @raise [ Error::UnsupportedFeatures ] If the wire version range is not covered
101
- # by the driver.
101
+ # @raise [ Error::UnsupportedFeatures ] If the wire version range is
102
+ # not covered by the driver.
102
103
  #
103
104
  # @since 2.5.1
104
105
  def check_driver_support!
@@ -1,4 +1,4 @@
1
- # Copyright (C) 2015 MongoDB, Inc.
1
+ # Copyright (C) 2015-2018 MongoDB, Inc.
2
2
  #
3
3
  # Licensed under the Apache License, Version 2.0 (the 'License');
4
4
  # you may not use this file except in compliance with the License.
@@ -1,4 +1,4 @@
1
- # Copyright (C) 2014-2017 MongoDB, Inc.
1
+ # Copyright (C) 2014-2018 MongoDB, Inc.
2
2
  #
3
3
  # Licensed under the Apache License, Version 2.0 (the 'License');
4
4
  # you may not use this file except in compliance with the License.
@@ -1,4 +1,4 @@
1
- # Copyright (C) 2015 MongoDB, Inc.
1
+ # Copyright (C) 2015-2018 MongoDB, Inc.
2
2
  #
3
3
  # Licensed under the Apache License, Version 2.0 (the 'License');
4
4
  # you may not use this file except in compliance with the License.
@@ -1,4 +1,4 @@
1
- # Copyright (C) 2015 MongoDB, Inc.
1
+ # Copyright (C) 2015-2018 MongoDB, Inc.
2
2
  #
3
3
  # Licensed under the Apache License, Version 2.0 (the 'License');
4
4
  # you may not use this file except in compliance with the License.
@@ -1,4 +1,4 @@
1
- # Copyright (C) 2015 MongoDB, Inc.
1
+ # Copyright (C) 2015-2018 MongoDB, Inc.
2
2
  #
3
3
  # Licensed under the Apache License, Version 2.0 (the 'License');
4
4
  # you may not use this file except in compliance with the License.
@@ -1,4 +1,4 @@
1
- # Copyright (C) 2014-2017 MongoDB Inc.
1
+ # Copyright (C) 2014-2018 MongoDB Inc.
2
2
  #
3
3
  # Licensed under the Apache License, Version 2.0 (the "License");
4
4
  # you may not use this file except in compliance with the License.
@@ -63,9 +63,19 @@ module Mongo
63
63
  # of the connection.
64
64
  def_delegators :connection, :compressor
65
65
 
66
- # Force the monitor to immediately do a check of its server.
66
+ # Perform a check of the server with throttling, and update
67
+ # the server's description.
67
68
  #
68
- # @example Force a scan.
69
+ # If the server was checked less than MIN_SCAN_FREQUENCY seconds
70
+ # ago, sleep until MIN_SCAN_FREQUENCY seconds have passed since the last
71
+ # check. Then perform the check which involves running isMaster
72
+ # on the server being monitored and updating the server description
73
+ # as a result.
74
+ #
75
+ # @note If the system clock is set to a time in the past, this method
76
+ # can sleep for a very long time.
77
+ #
78
+ # @example Run a scan.
69
79
  # monitor.scan!
70
80
  #
71
81
  # @return [ Description ] The updated description.
@@ -179,6 +189,8 @@ module Mongo
179
189
  end
180
190
  end
181
191
 
192
+ # @note If the system clock is set to a time in the past, this method
193
+ # can sleep for a very long time.
182
194
  def throttle_scan_frequency!
183
195
  if @last_scan
184
196
  difference = (Time.now - @last_scan)
@@ -1,4 +1,4 @@
1
- # Copyright (C) 2015 MongoDB, Inc.
1
+ # Copyright (C) 2015-2018 MongoDB, Inc.
2
2
 
3
3
  # Licensed under the Apache License, Version 2.0 (the "License");
4
4
  # you may not use this file except in compliance with the License.
@@ -1,4 +1,4 @@
1
- # Copyright (C) 2014-2017 MongoDB, Inc.
1
+ # Copyright (C) 2014-2018 MongoDB, Inc.
2
2
  #
3
3
  # Licensed under the Apache License, Version 2.0 (the "License");
4
4
  # you may not use this file except in compliance with the License.
@@ -1,4 +1,4 @@
1
- # Copyright (C) 2014-2017 MongoDB, Inc.
1
+ # Copyright (C) 2014-2018 MongoDB, Inc.
2
2
  #
3
3
  # Licensed under the Apache License, Version 2.0 (the "License");
4
4
  # you may not use this file except in compliance with the License.
@@ -1,4 +1,4 @@
1
- # Copyright (C) 2014-2017 MongoDB, Inc.
1
+ # Copyright (C) 2014-2018 MongoDB, Inc.
2
2
  #
3
3
  # Licensed under the Apache License, Version 2.0 (the "License");
4
4
  # you may not use this file except in compliance with the License.
@@ -1,4 +1,4 @@
1
- # Copyright (C) 2014-2017 MongoDB, Inc.
1
+ # Copyright (C) 2014-2018 MongoDB, Inc.
2
2
  #
3
3
  # Licensed under the Apache License, Version 2.0 (the "License");
4
4
  # you may not use this file except in compliance with the License.
@@ -1,4 +1,4 @@
1
- # Copyright (C) 2014-2017 MongoDB, Inc.
1
+ # Copyright (C) 2014-2018 MongoDB, Inc.
2
2
  #
3
3
  # Licensed under the Apache License, Version 2.0 (the "License");
4
4
  # you may not use this file except in compliance with the License.
@@ -1,4 +1,4 @@
1
- # Copyright (C) 2014-2017 MongoDB, Inc.
1
+ # Copyright (C) 2014-2018 MongoDB, Inc.
2
2
  #
3
3
  # Licensed under the Apache License, Version 2.0 (the "License");
4
4
  # you may not use this file except in compliance with the License.
@@ -1,4 +1,4 @@
1
- # Copyright (C) 2014-2017 MongoDB, Inc.
1
+ # Copyright (C) 2014-2018 MongoDB, Inc.
2
2
  #
3
3
  # Licensed under the Apache License, Version 2.0 (the "License");
4
4
  # you may not use this file except in compliance with the License.
@@ -102,7 +102,12 @@ module Mongo
102
102
  servers = candidates(cluster)
103
103
  if servers && !servers.compact.empty?
104
104
  server = servers.first
105
- server.check_driver_support!
105
+ # HACK: all servers in a topology must satisfy wire protocol
106
+ # constraints. There is probably a better implementation than
107
+ # checking all servers here
108
+ cluster.servers.each do |a_server|
109
+ a_server.check_driver_support!
110
+ end
106
111
  return server
107
112
  end
108
113
  cluster.scan!
@@ -1,4 +1,4 @@
1
- # Copyright (C) 2017 MongoDB, Inc.
1
+ # Copyright (C) 2017-2018 MongoDB, Inc.
2
2
  #
3
3
  # Licensed under the Apache License, Version 2.0 (the "License");
4
4
  # you may not use this file except in compliance with the License.
@@ -23,6 +23,7 @@ module Mongo
23
23
  # @since 2.5.0
24
24
  class Session
25
25
  extend Forwardable
26
+ include Retryable
26
27
 
27
28
  # Get the options for this session.
28
29
  #
@@ -32,7 +33,7 @@ module Mongo
32
33
  # Get the cluster through which this session was created.
33
34
  #
34
35
  # @since 2.5.1
35
- attr_reader :cluster
36
+ attr_reader :client
36
37
 
37
38
  # The cluster time for this session.
38
39
  #
@@ -44,6 +45,11 @@ module Mongo
44
45
  # @since 2.5.0
45
46
  attr_reader :operation_time
46
47
 
48
+ # The options for the transaction currently being executed on the session.
49
+ #
50
+ # @since 2.6.0
51
+ attr_reader :txn_options
52
+
47
53
  # Error message indicating that the session was retrieved from a client with a different cluster than that of the
48
54
  # client through which it is currently being used.
49
55
  #
@@ -62,21 +68,62 @@ module Mongo
62
68
  # @since 2.5.0
63
69
  SESSIONS_NOT_SUPPORTED = 'Sessions are not supported by the connected servers.'.freeze
64
70
 
71
+ # The state of a session in which the last operation was not related to any transaction or no
72
+ # operations have yet occurred.
73
+ #
74
+ # @since 2.6.0
75
+ NO_TRANSACTION_STATE = :no_transaction
76
+
77
+ # The state of a session in which a user has initiated a transaction but no operations within
78
+ # the transactions have occurred yet.
79
+ #
80
+ # @since 2.6.0
81
+ STARTING_TRANSACTION_STATE = :starting_transaction
82
+
83
+ # The state of a session in which a transaction has been started and at least one operation has
84
+ # occurred, but the transaction has not yet been committed or aborted.
85
+ #
86
+ # @since 2.6.0
87
+ TRANSACTION_IN_PROGRESS_STATE = :transaction_in_progress
88
+
89
+ # The state of a session in which the last operation executed was a transaction commit.
90
+ #
91
+ # @since 2.6.0
92
+ TRANSACTION_COMMITTED_STATE = :transaction_committed
93
+
94
+ # The state of a session in which the last operation executed was a transaction abort.
95
+ #
96
+ # @since 2.6.0
97
+ TRANSACTION_ABORTED_STATE = :transaction_aborted
98
+
99
+ UNLABELED_WRITE_CONCERN_CODES = [
100
+ 79, # UnknownReplWriteConcern
101
+ 100, # CannotSatisfyWriteConcern,
102
+ ].freeze
103
+
65
104
  # Initialize a Session.
66
105
  #
67
106
  # @example
68
- # Session.new(server_session, cluster, options)
107
+ # Session.new(server_session, client, options)
69
108
  #
70
109
  # @param [ ServerSession ] server_session The server session this session is associated with.
71
- # @param [ Cluster ] cluster The cluster through which this session is created.
110
+ # @param [ Client ] client The client through which this session is created.
72
111
  # @param [ Hash ] options The options for this session.
73
112
  #
74
113
  # @since 2.5.0
75
- def initialize(server_session, cluster, options = {})
114
+ def initialize(server_session, client, options = {})
76
115
  @server_session = server_session
77
- @cluster = cluster
116
+
117
+ # Because the read preference will need to be inserted into a command as a string, we convert
118
+ # it from a symbol immediately upon receiving it.
119
+ if options[:read_preference] && options[:read_preference][:mode]
120
+ options[:read_preference][:mode] = options[:read_preference][:mode].to_s
121
+ end
122
+
123
+ @client = client.use(:admin)
78
124
  @options = options.dup.freeze
79
125
  @cluster_time = nil
126
+ @state = NO_TRANSACTION_STATE
80
127
  end
81
128
 
82
129
  # Get a formatted string for use in inspection.
@@ -100,8 +147,9 @@ module Mongo
100
147
  #
101
148
  # @since 2.5.0
102
149
  def end_session
103
- if !ended? && @cluster
104
- @cluster.session_pool.checkin(@server_session)
150
+ if !ended? && @client
151
+ abort_transaction if within_states?(TRANSACTION_IN_PROGRESS_STATE) rescue Mongo::Error
152
+ @client.cluster.session_pool.checkin(@server_session)
105
153
  end
106
154
  ensure
107
155
  @server_session = nil
@@ -119,6 +167,20 @@ module Mongo
119
167
  @server_session.nil?
120
168
  end
121
169
 
170
+ # Add the autocommit field to a command document if applicable.
171
+ #
172
+ # @example
173
+ # session.add_autocommit!(cmd)
174
+ #
175
+ # @return [ Hash, BSON::Document ] The command document.
176
+ #
177
+ # @since 2.6.0
178
+ def add_autocommit!(command)
179
+ command.tap do |c|
180
+ c[:autocommit] = false if in_transaction?
181
+ end
182
+ end
183
+
122
184
  # Add this session's id to a command document.
123
185
  #
124
186
  # @example
@@ -131,6 +193,115 @@ module Mongo
131
193
  command.merge!(lsid: session_id)
132
194
  end
133
195
 
196
+ # Add the startTransaction field to a command document if applicable.
197
+ #
198
+ # @example
199
+ # session.add_start_transaction!(cmd)
200
+ #
201
+ # @return [ Hash, BSON::Document ] The command document.
202
+ #
203
+ # @since 2.6.0
204
+ def add_start_transaction!(command)
205
+ command.tap do |c|
206
+ c[:startTransaction] = true if starting_transaction?
207
+ end
208
+ end
209
+
210
+ # Add the transaction number to a command document if applicable.
211
+ #
212
+ # @example
213
+ # session.add_txn_num!(cmd)
214
+ #
215
+ # @return [ Hash, BSON::Document ] The command document.
216
+ #
217
+ # @since 2.6.0
218
+ def add_txn_num!(command)
219
+ command.tap do |c|
220
+ c[:txnNumber] = BSON::Int64.new(@server_session.txn_num) if in_transaction?
221
+ end
222
+ end
223
+
224
+ # Add the transactions options if applicable.
225
+ #
226
+ # @example
227
+ # session.add_txn_opts!(cmd)
228
+ #
229
+ # @return [ Hash, BSON::Document ] The command document.
230
+ #
231
+ # @since 2.6.0
232
+ def add_txn_opts!(command, read)
233
+ command.tap do |c|
234
+ # The read preference should be added for all read operations.
235
+ c['$readPreference'] = txn_read_pref if read && txn_read_pref
236
+
237
+ # The read concern should be added to any command that starts a transaction.
238
+ if starting_transaction? && txn_read_concern
239
+ c[:readConcern] ||= {}
240
+ c[:readConcern].merge!(txn_read_concern)
241
+ end
242
+
243
+ # We need to send the read concern level as a string rather than a symbol.
244
+ if c[:readConcern] && c[:readConcern][:level]
245
+ c[:readConcern][:level] = c[:readConcern][:level].to_s
246
+ end
247
+
248
+ # The write concern should be added to any abortTransaction or commitTransaction command.
249
+ if (c[:abortTransaction] || c[:commitTransaction]) && txn_write_concern
250
+ c[:writeConcern] = txn_write_concern
251
+ end
252
+
253
+ # A non-numeric write concern w value needs to be sent as a string rather than a symbol.
254
+ if c[:writeConcern] && c[:writeConcern][:w] && c[:writeConcern][:w].is_a?(Symbol)
255
+ c[:writeConcern][:w] = c[:writeConcern][:w].to_s
256
+ end
257
+ end
258
+ end
259
+
260
+ # Remove the read concern and/or write concern from the command if not applicable.
261
+ #
262
+ # @example
263
+ # session.suppress_read_write_concern!(cmd)
264
+ #
265
+ # @return [ Hash, BSON::Document ] The command document.
266
+ #
267
+ # @since 2.6.0
268
+ def suppress_read_write_concern!(command)
269
+ command.tap do |c|
270
+ next unless in_transaction?
271
+
272
+ c.delete(:readConcern) unless starting_transaction?
273
+ c.delete(:writeConcern) unless c[:commitTransaction] || c[:abortTransaction]
274
+ end
275
+ end
276
+
277
+ # Ensure that the read preference of a command primary.
278
+ #
279
+ # @example
280
+ # session.validate_read_pref!(command)
281
+ #
282
+ # @raise [ Mongo::Error::InvalidTransactionOperation ] If the read preference of the command is
283
+ # not primary.
284
+ #
285
+ # @since 2.6.0
286
+ def validate_read_pref!(command)
287
+ return unless in_transaction? && non_primary_readpref?(command)
288
+
289
+ raise Mongo::Error::InvalidTransactionOperation.new(
290
+ Mongo::Error::InvalidTransactionOperation::INVALID_READ_PREFERENCE)
291
+ end
292
+
293
+ # Update the state of the session due to a (non-commit and non-abort) operation being run.
294
+ #
295
+ # @since 2.6.0
296
+ def update_state!
297
+ case @state
298
+ when STARTING_TRANSACTION_STATE
299
+ @state = TRANSACTION_IN_PROGRESS_STATE
300
+ when TRANSACTION_COMMITTED_STATE, TRANSACTION_ABORTED_STATE
301
+ @state = NO_TRANSACTION_STATE
302
+ end
303
+ end
304
+
134
305
  # Validate the session.
135
306
  #
136
307
  # @example
@@ -203,7 +374,7 @@ module Mongo
203
374
  @operation_time = new_operation_time
204
375
  end
205
376
  end
206
-
377
+
207
378
  # Will writes executed with this session be retried.
208
379
  #
209
380
  # @example Will writes be retried.
@@ -211,8 +382,8 @@ module Mongo
211
382
  #
212
383
  # @return [ true, false ] If writes will be retried.
213
384
  #
214
- # @note Retryable writes are only available on server versions at least 3.6 and with
215
- # sharded clusters or replica sets.
385
+ # @note Retryable writes are only available on server versions at least 3.6
386
+ # and with sharded clusters or replica sets.
216
387
  #
217
388
  # @since 2.5.0
218
389
  def retry_writes?
@@ -243,6 +414,18 @@ module Mongo
243
414
  @server_session.next_txn_num if @server_session
244
415
  end
245
416
 
417
+ # Get the current transaction number.
418
+ #
419
+ # @example Get the current transaction number.
420
+ # session.txn_num
421
+ #
422
+ # @return [ Integer ] The current transaction number.
423
+ #
424
+ # @since 2.6.0
425
+ def txn_num
426
+ @server_session && @server_session.txn_num
427
+ end
428
+
246
429
  # Is this session an implicit one (not user-created).
247
430
  #
248
431
  # @example Is the session implicit?
@@ -267,8 +450,202 @@ module Mongo
267
450
  @explicit ||= !implicit?
268
451
  end
269
452
 
453
+ # Start a new transaction.
454
+ #
455
+ # Note that the transaction will not be started on the server until an operation is performed
456
+ # after start_transaction is called.
457
+ #
458
+ # @example Start a new transaction
459
+ # session.start_transaction(options)
460
+ #
461
+ # @raise [ InvalidTransactionOperation ] If a transaction is already in progress or if the
462
+ # write concern is unacknowledged.
463
+ #
464
+ # @since 2.6.0
465
+ def start_transaction(options = nil)
466
+ check_if_ended!
467
+
468
+ if within_states?(STARTING_TRANSACTION_STATE, TRANSACTION_IN_PROGRESS_STATE)
469
+ raise Mongo::Error::InvalidTransactionOperation.new(
470
+ Mongo::Error::InvalidTransactionOperation::TRANSACTION_ALREADY_IN_PROGRESS)
471
+ end
472
+
473
+ next_txn_num
474
+ @txn_options = options || @options[:default_transaction_options] || {}
475
+
476
+ if txn_write_concern && WriteConcern.send(:unacknowledged?, txn_write_concern)
477
+ raise Mongo::Error::InvalidTransactionOperation.new(
478
+ Mongo::Error::InvalidTransactionOperation::UNACKNOWLEDGED_WRITE_CONCERN)
479
+ end
480
+
481
+ @state = STARTING_TRANSACTION_STATE
482
+ end
483
+
484
+ # Commit the currently active transaction on the session.
485
+ #
486
+ # @example Commits the transaction.
487
+ # session.commit_transaction
488
+ #
489
+ # @raise [ InvalidTransactionOperation ] If a transaction was just aborted and no new one was
490
+ # started.
491
+ #
492
+ # @since 2.6.0
493
+ def commit_transaction
494
+ check_if_ended!
495
+ check_if_no_transaction!
496
+
497
+ if within_states?(TRANSACTION_ABORTED_STATE)
498
+ raise Mongo::Error::InvalidTransactionOperation.new(
499
+ Mongo::Error::InvalidTransactionOperation.cannot_call_after_msg(
500
+ :abortTransaction, :commitTransaction))
501
+ end
502
+
503
+ begin
504
+ # If commitTransaction is called twice, we need to run the same commit operation again, so
505
+ # we revert the session to the previous state.
506
+ if within_states?(TRANSACTION_COMMITTED_STATE)
507
+ @state = @last_commit_skipped ? STARTING_TRANSACTION_STATE : TRANSACTION_IN_PROGRESS_STATE
508
+ end
509
+
510
+ if starting_transaction?
511
+ @last_commit_skipped = true
512
+ else
513
+ @last_commit_skipped = false
514
+
515
+ write_with_retry(self, txn_options[:write_concern], true) do |server, txn_num|
516
+ Operation::Command.new(
517
+ selector: { commitTransaction: 1 },
518
+ db_name: 'admin',
519
+ session: self,
520
+ txn_num: txn_num
521
+ ).execute(server)
522
+ end
523
+ end
524
+ rescue Mongo::Error::NoServerAvailable, Mongo::Error::SocketError => e
525
+ e.send(:add_label, Mongo::Error::UNKNOWN_TRANSACTION_COMMIT_RESULT_LABEL)
526
+ raise e
527
+ rescue Mongo::Error::OperationFailure => e
528
+ err_doc = e.instance_variable_get(:@result).send(:first_document)
529
+
530
+ if e.write_retryable? || (err_doc['writeConcernError'] &&
531
+ !UNLABELED_WRITE_CONCERN_CODES.include?(err_doc['writeConcernError']['code']))
532
+ e.send(:add_label, Mongo::Error::UNKNOWN_TRANSACTION_COMMIT_RESULT_LABEL)
533
+ end
534
+
535
+ raise e
536
+ ensure
537
+ @state = TRANSACTION_COMMITTED_STATE
538
+ end
539
+ end
540
+
541
+ # Abort the currently active transaction without making any changes to the database.
542
+ #
543
+ # @example Abort the transaction.
544
+ # session.abort_transaction
545
+ #
546
+ # @raise [ Mongo::Error::InvalidTransactionOperation ] If a transaction was just committed or
547
+ # aborted and no new one was started.
548
+ #
549
+ # @since 2.6.0
550
+ def abort_transaction
551
+ check_if_ended!
552
+ check_if_no_transaction!
553
+
554
+ if within_states?(TRANSACTION_COMMITTED_STATE)
555
+ raise Mongo::Error::InvalidTransactionOperation.new(
556
+ Mongo::Error::InvalidTransactionOperation.cannot_call_after_msg(
557
+ :commitTransaction, :abortTransaction))
558
+ end
559
+
560
+ if within_states?(TRANSACTION_ABORTED_STATE)
561
+ raise Mongo::Error::InvalidTransactionOperation.new(
562
+ Mongo::Error::InvalidTransactionOperation.cannot_call_twice_msg(:abortTransaction))
563
+ end
564
+
565
+ begin
566
+ unless starting_transaction?
567
+ write_with_retry(self, txn_options[:write_concern], true) do |server, txn_num|
568
+ Operation::Command.new(
569
+ selector: { abortTransaction: 1 },
570
+ db_name: 'admin',
571
+ session: self,
572
+ txn_num: txn_num
573
+ ).execute(server)
574
+ end
575
+ end
576
+
577
+ @state = TRANSACTION_ABORTED_STATE
578
+ rescue Mongo::Error::InvalidTransactionOperation
579
+ raise
580
+ rescue Mongo::Error
581
+ @state = TRANSACTION_ABORTED_STATE
582
+ end
583
+ end
584
+
585
+ # Whether or not the session is currently in a transaction.
586
+ #
587
+ # @example Is the session in a transaction?
588
+ # session.in_transaction?
589
+ #
590
+ # @return [ true | false ] Whether or not the session in a transaction.
591
+ #
592
+ # @since 2.6.0
593
+ def in_transaction?
594
+ within_states?(STARTING_TRANSACTION_STATE, TRANSACTION_IN_PROGRESS_STATE)
595
+ end
596
+
597
+ # Get the read preference document the session will use in the currently active transaction.
598
+ #
599
+ # @example Get the transaction's read preference
600
+ # session.txn_read_pref
601
+ #
602
+ # @return [ Hash ] The read preference document of the transaction.
603
+ #
604
+ # @since 2.6.0
605
+ def txn_read_pref
606
+ rp = (txn_options && txn_options[:read_preference] && txn_options[:read_preference].dup) ||
607
+ (@client.read_preference && @client.read_preference.dup)
608
+ rp[:mode] = rp[:mode].to_s if rp
609
+ rp
610
+ end
611
+
612
+ def cluster
613
+ @client.cluster
614
+ end
615
+
270
616
  private
271
617
 
618
+ def within_states?(*states)
619
+ states.include?(@state)
620
+ end
621
+
622
+ def starting_transaction?
623
+ within_states?(STARTING_TRANSACTION_STATE)
624
+ end
625
+
626
+ def check_if_no_transaction!
627
+ return unless within_states?(NO_TRANSACTION_STATE)
628
+
629
+ raise Mongo::Error::InvalidTransactionOperation.new(
630
+ Mongo::Error::InvalidTransactionOperation::NO_TRANSACTION_STARTED)
631
+ end
632
+
633
+ def txn_read_concern
634
+ txn_options && txn_options[:read_concern] || @client.read_concern
635
+ end
636
+
637
+ def txn_write_concern
638
+ (txn_options && txn_options[:write_concern]) ||
639
+ (@client.write_concern && @client.write_concern.options)
640
+ end
641
+
642
+ def non_primary_readpref?(command)
643
+ return false unless command['$readPreference']
644
+
645
+ mode = command['$readPreference']['mode'] || command['$readPreference'][:mode]
646
+ mode && mode != 'primary'
647
+ end
648
+
272
649
  def causal_consistency_doc(read_concern)
273
650
  if operation_time && causal_consistency?
274
651
  (read_concern || {}).merge(:afterClusterTime => operation_time)
@@ -306,7 +683,7 @@ module Mongo
306
683
  end
307
684
 
308
685
  def check_matching_cluster!(cluster)
309
- if @cluster != cluster
686
+ if @client.cluster != cluster
310
687
  raise Mongo::Error::InvalidSession.new(MISMATCHED_CLUSTER_ERROR_MSG)
311
688
  end
312
689
  end