mongo 2.20.1 → 2.21.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 (246) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +3 -0
  3. data/Rakefile +2 -2
  4. data/lib/mongo/address.rb +22 -3
  5. data/lib/mongo/auth/aws/credentials_retriever.rb +70 -17
  6. data/lib/mongo/auth/base.rb +1 -1
  7. data/lib/mongo/bulk_write.rb +35 -2
  8. data/lib/mongo/client.rb +38 -6
  9. data/lib/mongo/client_encryption.rb +6 -3
  10. data/lib/mongo/cluster/reapers/cursor_reaper.rb +6 -1
  11. data/lib/mongo/cluster/sdam_flow.rb +20 -7
  12. data/lib/mongo/cluster.rb +14 -4
  13. data/lib/mongo/collection/helpers.rb +1 -1
  14. data/lib/mongo/collection/view/aggregation/behavior.rb +131 -0
  15. data/lib/mongo/collection/view/aggregation.rb +33 -99
  16. data/lib/mongo/collection/view/builder/aggregation.rb +1 -7
  17. data/lib/mongo/collection/view/change_stream.rb +80 -27
  18. data/lib/mongo/collection/view/iterable.rb +76 -60
  19. data/lib/mongo/collection/view/map_reduce.rb +25 -8
  20. data/lib/mongo/collection/view/readable.rb +79 -30
  21. data/lib/mongo/collection/view/writable.rb +109 -48
  22. data/lib/mongo/collection/view.rb +43 -3
  23. data/lib/mongo/collection.rb +158 -23
  24. data/lib/mongo/crypt/auto_encrypter.rb +4 -6
  25. data/lib/mongo/crypt/binding.rb +4 -4
  26. data/lib/mongo/crypt/context.rb +20 -14
  27. data/lib/mongo/crypt/encryption_io.rb +56 -26
  28. data/lib/mongo/crypt/explicit_encrypter.rb +49 -20
  29. data/lib/mongo/crypt/explicit_encryption_context.rb +17 -11
  30. data/lib/mongo/crypt/kms/azure/credentials_retriever.rb +22 -6
  31. data/lib/mongo/crypt/kms/gcp/credentials_retriever.rb +29 -4
  32. data/lib/mongo/csot_timeout_holder.rb +119 -0
  33. data/lib/mongo/cursor/kill_spec.rb +5 -2
  34. data/lib/mongo/cursor/nontailable.rb +27 -0
  35. data/lib/mongo/cursor.rb +86 -24
  36. data/lib/mongo/cursor_host.rb +82 -0
  37. data/lib/mongo/database/view.rb +81 -14
  38. data/lib/mongo/database.rb +88 -18
  39. data/lib/mongo/error/operation_failure.rb +209 -204
  40. data/lib/mongo/error/server_timeout_error.rb +12 -0
  41. data/lib/mongo/error/socket_timeout_error.rb +3 -1
  42. data/lib/mongo/error/timeout_error.rb +23 -0
  43. data/lib/mongo/error.rb +2 -0
  44. data/lib/mongo/grid/fs_bucket.rb +45 -12
  45. data/lib/mongo/grid/stream/read.rb +15 -1
  46. data/lib/mongo/grid/stream/write.rb +21 -4
  47. data/lib/mongo/index/view.rb +77 -16
  48. data/lib/mongo/operation/context.rb +40 -2
  49. data/lib/mongo/operation/create_search_indexes/op_msg.rb +2 -2
  50. data/lib/mongo/operation/delete/op_msg.rb +2 -1
  51. data/lib/mongo/operation/drop_search_index/op_msg.rb +2 -2
  52. data/lib/mongo/operation/find/op_msg.rb +45 -0
  53. data/lib/mongo/operation/get_more/op_msg.rb +33 -0
  54. data/lib/mongo/operation/insert/op_msg.rb +3 -2
  55. data/lib/mongo/operation/insert/result.rb +4 -2
  56. data/lib/mongo/operation/list_collections/result.rb +1 -1
  57. data/lib/mongo/operation/map_reduce/result.rb +1 -1
  58. data/lib/mongo/operation/op_msg_base.rb +3 -1
  59. data/lib/mongo/operation/result.rb +26 -5
  60. data/lib/mongo/operation/shared/executable.rb +12 -1
  61. data/lib/mongo/operation/shared/op_msg_executable.rb +4 -1
  62. data/lib/mongo/operation/shared/response_handling.rb +3 -3
  63. data/lib/mongo/operation/shared/sessions_supported.rb +1 -1
  64. data/lib/mongo/operation/shared/timed.rb +52 -0
  65. data/lib/mongo/operation/shared/write.rb +4 -1
  66. data/lib/mongo/operation/update/op_msg.rb +2 -1
  67. data/lib/mongo/operation/update_search_index/op_msg.rb +2 -2
  68. data/lib/mongo/operation.rb +1 -0
  69. data/lib/mongo/protocol/message.rb +1 -4
  70. data/lib/mongo/protocol/msg.rb +2 -2
  71. data/lib/mongo/retryable/read_worker.rb +69 -29
  72. data/lib/mongo/retryable/write_worker.rb +49 -18
  73. data/lib/mongo/retryable.rb +8 -2
  74. data/lib/mongo/server/connection.rb +11 -5
  75. data/lib/mongo/server/connection_base.rb +22 -2
  76. data/lib/mongo/server/connection_pool.rb +32 -14
  77. data/lib/mongo/server/description/features.rb +1 -1
  78. data/lib/mongo/server/description.rb +18 -5
  79. data/lib/mongo/server/monitor.rb +7 -4
  80. data/lib/mongo/server/pending_connection.rb +7 -3
  81. data/lib/mongo/server/{round_trip_time_averager.rb → round_trip_time_calculator.rb} +25 -7
  82. data/lib/mongo/server.rb +11 -6
  83. data/lib/mongo/server_selector/base.rb +25 -9
  84. data/lib/mongo/session.rb +78 -9
  85. data/lib/mongo/socket/ssl.rb +109 -17
  86. data/lib/mongo/socket/tcp.rb +40 -6
  87. data/lib/mongo/socket.rb +154 -25
  88. data/lib/mongo/uri/options_mapper.rb +1 -0
  89. data/lib/mongo/version.rb +1 -1
  90. data/lib/mongo.rb +1 -0
  91. data/spec/atlas/atlas_connectivity_spec.rb +4 -0
  92. data/spec/atlas/operations_spec.rb +4 -0
  93. data/spec/integration/client_side_encryption/auto_encryption_mongocryptd_spawn_spec.rb +2 -1
  94. data/spec/integration/client_side_encryption/auto_encryption_spec.rb +494 -487
  95. data/spec/integration/client_side_encryption/on_demand_aws_credentials_spec.rb +1 -1
  96. data/spec/integration/client_side_encryption/range_explicit_encryption_prose_spec.rb +66 -22
  97. data/spec/integration/client_side_operations_timeout/encryption_prose_spec.rb +131 -0
  98. data/spec/integration/connection_pool_populator_spec.rb +2 -0
  99. data/spec/integration/cursor_pinning_spec.rb +15 -60
  100. data/spec/integration/cursor_reaping_spec.rb +1 -1
  101. data/spec/integration/docs_examples_spec.rb +1 -1
  102. data/spec/integration/operation_failure_code_spec.rb +1 -1
  103. data/spec/integration/operation_failure_message_spec.rb +3 -3
  104. data/spec/integration/retryable_errors_spec.rb +2 -2
  105. data/spec/integration/sdam_error_handling_spec.rb +2 -1
  106. data/spec/integration/search_indexes_prose_spec.rb +4 -0
  107. data/spec/integration/server_spec.rb +4 -3
  108. data/spec/integration/transactions_api_examples_spec.rb +2 -0
  109. data/spec/kerberos/kerberos_spec.rb +4 -0
  110. data/spec/lite_spec_helper.rb +3 -1
  111. data/spec/mongo/auth/user/view_spec.rb +1 -1
  112. data/spec/mongo/caching_cursor_spec.rb +1 -1
  113. data/spec/mongo/client_encryption_spec.rb +1 -0
  114. data/spec/mongo/client_spec.rb +158 -4
  115. data/spec/mongo/collection/view/aggregation_spec.rb +14 -39
  116. data/spec/mongo/collection/view/change_stream_spec.rb +3 -3
  117. data/spec/mongo/collection_spec.rb +5 -6
  118. data/spec/mongo/crypt/auto_encrypter_spec.rb +14 -12
  119. data/spec/mongo/crypt/data_key_context_spec.rb +3 -1
  120. data/spec/mongo/crypt/explicit_encryption_context_spec.rb +2 -2
  121. data/spec/mongo/crypt/handle_spec.rb +1 -1
  122. data/spec/mongo/cursor_spec.rb +26 -9
  123. data/spec/mongo/error/operation_failure_heavy_spec.rb +2 -2
  124. data/spec/mongo/operation/context_spec.rb +79 -0
  125. data/spec/mongo/operation/create/op_msg_spec.rb +106 -110
  126. data/spec/mongo/operation/delete/op_msg_spec.rb +6 -5
  127. data/spec/mongo/operation/find/op_msg_spec.rb +66 -0
  128. data/spec/mongo/operation/get_more/op_msg_spec.rb +65 -0
  129. data/spec/mongo/operation/insert/op_msg_spec.rb +128 -131
  130. data/spec/mongo/operation/shared/csot/examples.rb +113 -0
  131. data/spec/mongo/query_cache_spec.rb +243 -225
  132. data/spec/mongo/retryable_spec.rb +1 -0
  133. data/spec/mongo/server/round_trip_time_calculator_spec.rb +120 -0
  134. data/spec/mongo/socket/ssl_spec.rb +0 -10
  135. data/spec/runners/change_streams/test.rb +2 -2
  136. data/spec/runners/crud/operation.rb +1 -1
  137. data/spec/runners/crud/verifier.rb +3 -1
  138. data/spec/runners/transactions/operation.rb +4 -6
  139. data/spec/runners/unified/ambiguous_operations.rb +13 -0
  140. data/spec/runners/unified/assertions.rb +4 -0
  141. data/spec/runners/unified/change_stream_operations.rb +14 -24
  142. data/spec/runners/unified/crud_operations.rb +82 -59
  143. data/spec/runners/unified/ddl_operations.rb +38 -7
  144. data/spec/runners/unified/grid_fs_operations.rb +37 -2
  145. data/spec/runners/unified/support_operations.rb +43 -4
  146. data/spec/runners/unified/test.rb +22 -10
  147. data/spec/runners/unified.rb +1 -1
  148. data/spec/solo/clean_exit_spec.rb +2 -0
  149. data/spec/spec_tests/client_side_operations_timeout_spec.rb +15 -0
  150. data/spec/spec_tests/data/change_streams_unified/change-streams-clusterTime.yml +3 -1
  151. data/spec/spec_tests/data/change_streams_unified/change-streams-disambiguatedPaths.yml +3 -1
  152. data/spec/spec_tests/data/change_streams_unified/change-streams-errors.yml +3 -1
  153. data/spec/spec_tests/data/change_streams_unified/change-streams-pre_and_post_images.yml +1 -1
  154. data/spec/spec_tests/data/change_streams_unified/change-streams-resume-allowlist.yml +1 -1
  155. data/spec/spec_tests/data/change_streams_unified/change-streams-resume-errorLabels.yml +1 -1
  156. data/spec/spec_tests/data/change_streams_unified/change-streams-showExpandedEvents.yml +1 -1
  157. data/spec/spec_tests/data/client_side_encryption/badQueries.yml +2 -1
  158. data/spec/spec_tests/data/client_side_encryption/timeoutMS.yml +67 -0
  159. data/spec/spec_tests/data/client_side_operations_timeout/bulkWrite.yml +87 -0
  160. data/spec/spec_tests/data/client_side_operations_timeout/change-streams.yml +358 -0
  161. data/spec/spec_tests/data/client_side_operations_timeout/close-cursors.yml +129 -0
  162. data/spec/spec_tests/data/client_side_operations_timeout/command-execution.yml +250 -0
  163. data/spec/spec_tests/data/client_side_operations_timeout/convenient-transactions.yml +113 -0
  164. data/spec/spec_tests/data/client_side_operations_timeout/cursors.yml +70 -0
  165. data/spec/spec_tests/data/client_side_operations_timeout/deprecated-options.yml +3982 -0
  166. data/spec/spec_tests/data/client_side_operations_timeout/error-transformations.yml +96 -0
  167. data/spec/spec_tests/data/client_side_operations_timeout/global-timeoutMS.yml +3236 -0
  168. data/spec/spec_tests/data/client_side_operations_timeout/gridfs-advanced.yml +207 -0
  169. data/spec/spec_tests/data/client_side_operations_timeout/gridfs-delete.yml +152 -0
  170. data/spec/spec_tests/data/client_side_operations_timeout/gridfs-download.yml +182 -0
  171. data/spec/spec_tests/data/client_side_operations_timeout/gridfs-find.yml +100 -0
  172. data/spec/spec_tests/data/client_side_operations_timeout/gridfs-upload.yml +249 -0
  173. data/spec/spec_tests/data/client_side_operations_timeout/legacy-timeouts.yml +204 -0
  174. data/spec/spec_tests/data/client_side_operations_timeout/non-tailable-cursors.yml +307 -0
  175. data/spec/spec_tests/data/client_side_operations_timeout/override-collection-timeoutMS.yml +1877 -0
  176. data/spec/spec_tests/data/client_side_operations_timeout/override-operation-timeoutMS.yml +1918 -0
  177. data/spec/spec_tests/data/client_side_operations_timeout/retryability-legacy-timeouts.yml +1676 -0
  178. data/spec/spec_tests/data/client_side_operations_timeout/retryability-timeoutMS.yml +2824 -0
  179. data/spec/spec_tests/data/client_side_operations_timeout/sessions-inherit-timeoutMS.yml +168 -0
  180. data/spec/spec_tests/data/client_side_operations_timeout/sessions-override-operation-timeoutMS.yml +171 -0
  181. data/spec/spec_tests/data/client_side_operations_timeout/sessions-override-timeoutMS.yml +168 -0
  182. data/spec/spec_tests/data/client_side_operations_timeout/tailable-awaitData.yml +247 -0
  183. data/spec/spec_tests/data/client_side_operations_timeout/tailable-non-awaitData.yml +181 -0
  184. data/spec/spec_tests/data/crud_unified/aggregate-write-readPreference.yml +4 -0
  185. data/spec/spec_tests/data/crud_unified/db-aggregate-write-readPreference.yml +4 -0
  186. data/spec/spec_tests/data/crud_unified/find-test-all-options.yml +29 -0
  187. data/spec/spec_tests/server_selection_rtt_spec.rb +6 -6
  188. data/spec/support/certificates/atlas-ocsp-ca.crt +81 -83
  189. data/spec/support/certificates/atlas-ocsp.crt +107 -107
  190. data/spec/support/cluster_tools.rb +3 -3
  191. data/spec/support/common_shortcuts.rb +2 -2
  192. data/spec/support/crypt/encrypted_fields/range-encryptedFields-Date.json +1 -1
  193. data/spec/support/crypt/encrypted_fields/range-encryptedFields-DecimalNoPrecision.json +1 -1
  194. data/spec/support/crypt/encrypted_fields/range-encryptedFields-DecimalPrecision.json +1 -1
  195. data/spec/support/crypt/encrypted_fields/range-encryptedFields-DoubleNoPrecision.json +1 -1
  196. data/spec/support/crypt/encrypted_fields/range-encryptedFields-DoublePrecision.json +1 -1
  197. data/spec/support/crypt/encrypted_fields/range-encryptedFields-Int.json +1 -1
  198. data/spec/support/crypt/encrypted_fields/range-encryptedFields-Long.json +1 -1
  199. data/spec/support/shared/session.rb +2 -2
  200. data/spec/support/spec_setup.rb +2 -2
  201. data/spec/support/utils.rb +3 -1
  202. metadata +78 -91
  203. data/spec/mongo/server/round_trip_time_averager_spec.rb +0 -48
  204. data/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Date-Aggregate.yml +0 -242
  205. data/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Date-Correctness.yml +0 -423
  206. data/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Date-Delete.yml +0 -183
  207. data/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Date-FindOneAndUpdate.yml +0 -240
  208. data/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Date-InsertFind.yml +0 -236
  209. data/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Date-Update.yml +0 -253
  210. data/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Decimal-Aggregate.yml +0 -1688
  211. data/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Decimal-Correctness.yml +0 -294
  212. data/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Decimal-Delete.yml +0 -906
  213. data/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Decimal-FindOneAndUpdate.yml +0 -1685
  214. data/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Decimal-InsertFind.yml +0 -1681
  215. data/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Decimal-Update.yml +0 -1698
  216. data/spec/spec_tests/data/client_side_encryption/fle2v2-Range-DecimalPrecision-Aggregate.yml +0 -330
  217. data/spec/spec_tests/data/client_side_encryption/fle2v2-Range-DecimalPrecision-Correctness.yml +0 -425
  218. data/spec/spec_tests/data/client_side_encryption/fle2v2-Range-DecimalPrecision-Delete.yml +0 -227
  219. data/spec/spec_tests/data/client_side_encryption/fle2v2-Range-DecimalPrecision-FindOneAndUpdate.yml +0 -328
  220. data/spec/spec_tests/data/client_side_encryption/fle2v2-Range-DecimalPrecision-InsertFind.yml +0 -320
  221. data/spec/spec_tests/data/client_side_encryption/fle2v2-Range-DecimalPrecision-Update.yml +0 -337
  222. data/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Double-Aggregate.yml +0 -914
  223. data/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Double-Correctness.yml +0 -293
  224. data/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Double-Delete.yml +0 -519
  225. data/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Double-FindOneAndUpdate.yml +0 -912
  226. data/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Double-InsertFind.yml +0 -908
  227. data/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Double-Update.yml +0 -925
  228. data/spec/spec_tests/data/client_side_encryption/fle2v2-Range-DoublePrecision-Aggregate.yml +0 -326
  229. data/spec/spec_tests/data/client_side_encryption/fle2v2-Range-DoublePrecision-Correctness.yml +0 -425
  230. data/spec/spec_tests/data/client_side_encryption/fle2v2-Range-DoublePrecision-Delete.yml +0 -225
  231. data/spec/spec_tests/data/client_side_encryption/fle2v2-Range-DoublePrecision-FindOneAndUpdate.yml +0 -324
  232. data/spec/spec_tests/data/client_side_encryption/fle2v2-Range-DoublePrecision-InsertFind.yml +0 -320
  233. data/spec/spec_tests/data/client_side_encryption/fle2v2-Range-DoublePrecision-Update.yml +0 -339
  234. data/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Int-Aggregate.yml +0 -242
  235. data/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Int-Correctness.yml +0 -424
  236. data/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Int-Delete.yml +0 -183
  237. data/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Int-FindOneAndUpdate.yml +0 -240
  238. data/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Int-InsertFind.yml +0 -236
  239. data/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Int-Update.yml +0 -255
  240. data/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Long-Aggregate.yml +0 -242
  241. data/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Long-Correctness.yml +0 -423
  242. data/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Long-Delete.yml +0 -183
  243. data/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Long-FindOneAndUpdate.yml +0 -240
  244. data/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Long-InsertFind.yml +0 -236
  245. data/spec/spec_tests/data/client_side_encryption/fle2v2-Range-Long-Update.yml +0 -255
  246. data/spec/spec_tests/data/client_side_encryption/fle2v2-Range-WrongType.yml +0 -44
@@ -0,0 +1,131 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mongo
4
+ class Collection
5
+ class View
6
+ class Aggregation
7
+ # Distills the behavior common to aggregator classes, like
8
+ # View::Aggregator and View::ChangeStream.
9
+ module Behavior
10
+ extend Forwardable
11
+ include Enumerable
12
+ include Immutable
13
+ include Iterable
14
+ include Explainable
15
+ include Loggable
16
+ include Retryable
17
+
18
+ # @return [ View ] view The collection view.
19
+ attr_reader :view
20
+
21
+ # Delegate necessary operations to the view.
22
+ def_delegators :view, :collection, :read, :cluster, :cursor_type, :limit, :batch_size
23
+
24
+ # Delegate necessary operations to the collection.
25
+ def_delegators :collection, :database, :client
26
+
27
+ # Set to true if disk usage is allowed during the aggregation.
28
+ #
29
+ # @example Set disk usage flag.
30
+ # aggregation.allow_disk_use(true)
31
+ #
32
+ # @param [ true, false ] value The flag value.
33
+ #
34
+ # @return [ true, false, Aggregation ] The aggregation if a value was
35
+ # set or the value if used as a getter.
36
+ #
37
+ # @since 2.0.0
38
+ def allow_disk_use(value = nil)
39
+ configure(:allow_disk_use, value)
40
+ end
41
+
42
+ # Get the explain plan for the aggregation.
43
+ #
44
+ # @example Get the explain plan for the aggregation.
45
+ # aggregation.explain
46
+ #
47
+ # @return [ Hash ] The explain plan.
48
+ #
49
+ # @since 2.0.0
50
+ def explain
51
+ self.class.new(view, pipeline, options.merge(explain: true)).first
52
+ end
53
+
54
+ # Whether this aggregation will write its result to a database collection.
55
+ #
56
+ # @return [ Boolean ] Whether the aggregation will write its result
57
+ # to a collection.
58
+ #
59
+ # @api private
60
+ def write?
61
+ pipeline.any? { |op| op.key?('$out') || op.key?(:$out) || op.key?('$merge') || op.key?(:$merge) }
62
+ end
63
+
64
+ # @return [ Integer | nil ] the timeout_ms value that was passed as
65
+ # an option to this object, or which was inherited from the view.
66
+ #
67
+ # @api private
68
+ def timeout_ms
69
+ @timeout_ms || view.timeout_ms
70
+ end
71
+
72
+ private
73
+
74
+ # Common setup for all classes that include this behavior; the
75
+ # constructor should invoke this method.
76
+ def perform_setup(view, options, forbid: [])
77
+ @view = view
78
+
79
+ @timeout_ms = options.delete(:timeout_ms)
80
+ @options = BSON::Document.new(options).freeze
81
+
82
+ yield
83
+
84
+ validate_timeout_mode!(options, forbid: forbid)
85
+ end
86
+
87
+ def server_selector
88
+ @view.send(:server_selector)
89
+ end
90
+
91
+ def aggregate_spec(session, read_preference)
92
+ Builder::Aggregation.new(
93
+ pipeline,
94
+ view,
95
+ options.merge(session: session, read_preference: read_preference)
96
+ ).specification
97
+ end
98
+
99
+ # Skip, sort, limit, projection are specified as pipeline stages
100
+ # rather than as options.
101
+ def cache_options
102
+ {
103
+ namespace: collection.namespace,
104
+ selector: pipeline,
105
+ read_concern: view.read_concern,
106
+ read_preference: view.read_preference,
107
+ collation: options[:collation],
108
+ # Aggregations can read documents from more than one collection,
109
+ # so they will be cleared on every write operation.
110
+ multi_collection: true,
111
+ }
112
+ end
113
+
114
+ # @return [ Hash ] timeout_ms value set on the operation level (if any),
115
+ # and/or timeout_ms that is set on collection/database/client level (if any).
116
+ #
117
+ # @api private
118
+ def operation_timeouts(opts = {})
119
+ {}.tap do |result|
120
+ if opts[:timeout_ms] || @timeout_ms
121
+ result[:operation_timeout_ms] = opts.delete(:timeout_ms) || @timeout_ms
122
+ else
123
+ result[:inherited_timeout_ms] = view.timeout_ms
124
+ end
125
+ end
126
+ end
127
+ end
128
+ end
129
+ end
130
+ end
131
+ end
@@ -15,6 +15,8 @@
15
15
  # See the License for the specific language governing permissions and
16
16
  # limitations under the License.
17
17
 
18
+ require 'mongo/collection/view/aggregation/behavior'
19
+
18
20
  module Mongo
19
21
  class Collection
20
22
  class View
@@ -23,46 +25,11 @@ module Mongo
23
25
  #
24
26
  # @since 2.0.0
25
27
  class Aggregation
26
- extend Forwardable
27
- include Enumerable
28
- include Immutable
29
- include Iterable
30
- include Explainable
31
- include Loggable
32
- include Retryable
28
+ include Behavior
33
29
 
34
- # @return [ View ] view The collection view.
35
- attr_reader :view
36
30
  # @return [ Array<Hash> ] pipeline The aggregation pipeline.
37
31
  attr_reader :pipeline
38
32
 
39
- # Delegate necessary operations to the view.
40
- def_delegators :view, :collection, :read, :cluster
41
-
42
- # Delegate necessary operations to the collection.
43
- def_delegators :collection, :database, :client
44
-
45
- # The reroute message.
46
- #
47
- # @since 2.1.0
48
- # @deprecated
49
- REROUTE = 'Rerouting the Aggregation operation to the primary server.'.freeze
50
-
51
- # Set to true if disk usage is allowed during the aggregation.
52
- #
53
- # @example Set disk usage flag.
54
- # aggregation.allow_disk_use(true)
55
- #
56
- # @param [ true, false ] value The flag value.
57
- #
58
- # @return [ true, false, Aggregation ] The aggregation if a value was
59
- # set or the value if used as a getter.
60
- #
61
- # @since 2.0.0
62
- def allow_disk_use(value = nil)
63
- configure(:allow_disk_use, value)
64
- end
65
-
66
33
  # Initialize the aggregation for the provided collection view, pipeline
67
34
  # and options.
68
35
  #
@@ -86,59 +53,29 @@ module Mongo
86
53
  # @option options [ Hash ] :let Mapping of variables to use in the pipeline.
87
54
  # See the server documentation for details.
88
55
  # @option options [ Integer ] :max_time_ms The maximum amount of time in
89
- # milliseconds to allow the aggregation to run.
90
- # @option options [ true, false ] :use_cursor Indicates whether the command
91
- # will request that the server provide results using a cursor. Note that
92
- # as of server version 3.6, aggregations always provide results using a
93
- # cursor and this option is therefore not valid.
56
+ # milliseconds to allow the aggregation to run. This option is deprecated, use
57
+ # :timeout_ms instead.
94
58
  # @option options [ Session ] :session The session to use.
59
+ # @option options [ :cursor_lifetime | :iteration ] :timeout_mode How to interpret
60
+ # :timeout_ms (whether it applies to the lifetime of the cursor, or per
61
+ # iteration).
62
+ # @option options [ Integer ] :timeout_ms The operation timeout in milliseconds.
63
+ # Must be a non-negative integer. An explicit value of 0 means infinite.
64
+ # The default value is unset which means the value is inherited from
65
+ # the collection or the database or the client.
95
66
  #
96
67
  # @since 2.0.0
97
68
  def initialize(view, pipeline, options = {})
98
- @view = view
99
- @pipeline = pipeline.dup
100
- unless Mongo.broken_view_aggregate || view.filter.empty?
101
- @pipeline.unshift(:$match => view.filter)
69
+ perform_setup(view, options) do
70
+ @pipeline = pipeline.dup
71
+ unless Mongo.broken_view_aggregate || view.filter.empty?
72
+ @pipeline.unshift(:$match => view.filter)
73
+ end
102
74
  end
103
- @options = BSON::Document.new(options).freeze
104
- end
105
-
106
- # Get the explain plan for the aggregation.
107
- #
108
- # @example Get the explain plan for the aggregation.
109
- # aggregation.explain
110
- #
111
- # @return [ Hash ] The explain plan.
112
- #
113
- # @since 2.0.0
114
- def explain
115
- self.class.new(view, pipeline, options.merge(explain: true)).first
116
- end
117
-
118
- # Whether this aggregation will write its result to a database collection.
119
- #
120
- # @return [ Boolean ] Whether the aggregation will write its result
121
- # to a collection.
122
- #
123
- # @api private
124
- def write?
125
- pipeline.any? { |op| op.key?('$out') || op.key?(:$out) || op.key?('$merge') || op.key?(:$merge) }
126
75
  end
127
76
 
128
77
  private
129
78
 
130
- def server_selector
131
- @view.send(:server_selector)
132
- end
133
-
134
- def aggregate_spec(session, read_preference)
135
- Builder::Aggregation.new(
136
- pipeline,
137
- view,
138
- options.merge(session: session, read_preference: read_preference)
139
- ).specification
140
- end
141
-
142
79
  def new(options)
143
80
  Aggregation.new(view, pipeline, options)
144
81
  end
@@ -180,32 +117,29 @@ module Mongo
180
117
 
181
118
  end
182
119
 
183
- def send_initial_query(server, session)
184
- server.with_connection do |connection|
120
+ def send_initial_query(server, context)
121
+ if server.load_balancer?
122
+ # Connection will be checked in when cursor is drained.
123
+ connection = server.pool.check_out(context: context)
185
124
  initial_query_op(
186
- session,
125
+ context.session,
187
126
  effective_read_preference(connection)
188
127
  ).execute_with_connection(
189
128
  connection,
190
- context: Operation::Context.new(client: client, session: session)
129
+ context: context
191
130
  )
131
+ else
132
+ server.with_connection do |connection|
133
+ initial_query_op(
134
+ context.session,
135
+ effective_read_preference(connection)
136
+ ).execute_with_connection(
137
+ connection,
138
+ context: context
139
+ )
140
+ end
192
141
  end
193
142
  end
194
-
195
- # Skip, sort, limit, projection are specified as pipeline stages
196
- # rather than as options.
197
- def cache_options
198
- {
199
- namespace: collection.namespace,
200
- selector: pipeline,
201
- read_concern: view.read_concern,
202
- read_preference: view.read_preference,
203
- collation: options[:collation],
204
- # Aggregations can read documents from more than one collection,
205
- # so they will be cleared on every write operation.
206
- multi_collection: true,
207
- }
208
- end
209
143
  end
210
144
  end
211
145
  end
@@ -113,17 +113,11 @@ module Mongo
113
113
  command[:readConcern] = Options::Mapper.transform_values_to_strings(
114
114
  read_concern)
115
115
  end
116
- command[:cursor] = cursor if cursor
116
+ command[:cursor] = batch_size_doc
117
117
  command.merge!(Options::Mapper.transform_documents(options, MAPPINGS))
118
118
  command
119
119
  end
120
120
 
121
- def cursor
122
- if options[:use_cursor] == true || options[:use_cursor].nil?
123
- batch_size_doc
124
- end
125
- end
126
-
127
121
  def batch_size_doc
128
122
  value = options[:batch_size] || view.batch_size
129
123
  if value == 0 && write?
@@ -15,6 +15,7 @@
15
15
  # See the License for the specific language governing permissions and
16
16
  # limitations under the License.
17
17
 
18
+ require 'mongo/collection/view/aggregation/behavior'
18
19
  require 'mongo/collection/view/change_stream/retryable'
19
20
 
20
21
  module Mongo
@@ -35,7 +36,8 @@ module Mongo
35
36
  #
36
37
  #
37
38
  # @since 2.5.0
38
- class ChangeStream < Aggregation
39
+ class ChangeStream
40
+ include Aggregation::Behavior
39
41
  include Retryable
40
42
 
41
43
  # @return [ String ] The fullDocument option default value.
@@ -60,6 +62,10 @@ module Mongo
60
62
  # @since 2.5.0
61
63
  attr_reader :options
62
64
 
65
+ # @return [ Cursor ] the underlying cursor for this operation
66
+ # @api private
67
+ attr_reader :cursor
68
+
63
69
  # Initialize the change stream for the provided collection view, pipeline
64
70
  # and options.
65
71
  #
@@ -125,11 +131,13 @@ module Mongo
125
131
  #
126
132
  # @since 2.5.0
127
133
  def initialize(view, pipeline, changes_for, options = {})
128
- @view = view
129
- @changes_for = changes_for
130
- @change_stream_filters = pipeline && pipeline.dup
131
- @options = options && options.dup.freeze
132
- @start_after = @options[:start_after]
134
+ # change stream cursors can only be :iterable, so we don't allow
135
+ # timeout_mode to be specified.
136
+ perform_setup(view, options, forbid: %i[ timeout_mode ]) do
137
+ @changes_for = changes_for
138
+ @change_stream_filters = pipeline && pipeline.dup
139
+ @start_after = @options[:start_after]
140
+ end
133
141
 
134
142
  # The resume token tracked by the change stream, used only
135
143
  # when there is no cursor, or no cursor resume token
@@ -181,24 +189,30 @@ module Mongo
181
189
  # @return [ BSON::Document | nil ] A change stream document.
182
190
  # @since 2.6.0
183
191
  def try_next
192
+ recreate_cursor! if @timed_out
193
+
184
194
  raise StopIteration.new if closed?
195
+
185
196
  begin
186
197
  doc = @cursor.try_next
187
198
  rescue Mongo::Error => e
188
- if !e.change_stream_resumable?
189
- raise
190
- end
191
-
192
- # Rerun initial aggregation.
193
- # Any errors here will stop iteration and break out of this
194
- # method.
199
+ # "If a next call fails with a timeout error, drivers MUST NOT
200
+ # invalidate the change stream. The subsequent next call MUST
201
+ # perform a resume attempt to establish a new change stream on the
202
+ # server..."
203
+ #
204
+ # However, SocketTimeoutErrors are TimeoutErrors, but are also
205
+ # change-stream-resumable. To preserve existing (specified) behavior,
206
+ # We only count timeouts when the error is not also
207
+ # change-stream-resumable.
208
+ @timed_out = e.is_a?(Mongo::Error::TimeoutError) && !e.change_stream_resumable?
209
+
210
+ raise unless @timed_out || e.change_stream_resumable?
195
211
 
196
- # Save cursor's resume token so we can use it
197
- # to create a new cursor
198
212
  @resume_token = @cursor.resume_token
213
+ raise e if @timed_out
199
214
 
200
- close
201
- create_cursor!
215
+ recreate_cursor!(@cursor.context)
202
216
  retry
203
217
  end
204
218
 
@@ -231,14 +245,17 @@ module Mongo
231
245
  # This method ignores any errors that occur when closing the
232
246
  # server-side cursor.
233
247
  #
248
+ # @params [ Hash ] opts Options to be passed to the cursor close
249
+ # command.
250
+ #
234
251
  # @return [ nil ] Always nil.
235
252
  #
236
253
  # @since 2.5.0
237
- def close
254
+ def close(opts = {})
238
255
  unless closed?
239
256
  begin
240
- @cursor.close
241
- rescue Error::OperationFailure, Error::SocketError, Error::SocketTimeoutError, Error::MissingConnection
257
+ @cursor.close(opts)
258
+ rescue Error::OperationFailure::Family, Error::SocketError, Error::SocketTimeoutError, Error::MissingConnection
242
259
  # ignore
243
260
  end
244
261
  @cursor = nil
@@ -284,6 +301,28 @@ module Mongo
284
301
  cursor_resume_token || @resume_token
285
302
  end
286
303
 
304
+ # "change streams are an abstraction around tailable-awaitData cursors..."
305
+ #
306
+ # @return :tailable_await
307
+ def cursor_type
308
+ :tailable_await
309
+ end
310
+
311
+ # "change streams...implicitly use ITERATION mode"
312
+ #
313
+ # @return :iteration
314
+ def timeout_mode
315
+ :iteration
316
+ end
317
+
318
+ # Returns the value of the max_await_time_ms option that was
319
+ # passed to this change stream.
320
+ #
321
+ # @return [ Integer | nil ] the max_await_time_ms value
322
+ def max_await_time_ms
323
+ options[:max_await_time_ms]
324
+ end
325
+
287
326
  private
288
327
 
289
328
  def for_cluster?
@@ -298,19 +337,23 @@ module Mongo
298
337
  !for_cluster? && !for_database?
299
338
  end
300
339
 
301
- def create_cursor!
340
+ def create_cursor!(timeout_ms = nil)
302
341
  # clear the cache because we may get a newer or an older server
303
342
  # (rolling upgrades)
304
343
  @start_at_operation_time_supported = nil
305
344
 
306
- session = client.send(:get_session, @options)
345
+ session = client.get_session(@options)
346
+ context = Operation::Context.new(client: client, session: session, view: self, operation_timeouts: timeout_ms ? { operation_timeout_ms: timeout_ms } : operation_timeouts)
347
+
307
348
  start_at_operation_time = nil
308
349
  start_at_operation_time_supported = nil
309
- @cursor = read_with_retry_cursor(session, server_selector, view) do |server|
350
+
351
+ @cursor = read_with_retry_cursor(session, server_selector, self, context: context) do |server|
310
352
  server.with_connection do |connection|
311
353
  start_at_operation_time_supported = connection.description.server_version_gte?('4.0')
312
354
 
313
- result = send_initial_query(connection, session)
355
+ result = send_initial_query(connection, context)
356
+
314
357
  if doc = result.replies.first && result.replies.first.documents.first
315
358
  start_at_operation_time = doc['operationTime']
316
359
  else
@@ -324,6 +367,7 @@ module Mongo
324
367
  result
325
368
  end
326
369
  end
370
+
327
371
  @start_at_operation_time = start_at_operation_time
328
372
  @start_at_operation_time_supported = start_at_operation_time_supported
329
373
  end
@@ -390,11 +434,11 @@ module Mongo
390
434
  end
391
435
  end
392
436
 
393
- def send_initial_query(connection, session)
394
- initial_query_op(session, view.read_preference)
437
+ def send_initial_query(connection, context)
438
+ initial_query_op(context.session, view.read_preference)
395
439
  .execute_with_connection(
396
440
  connection,
397
- context: Operation::Context.new(client: client, session: session),
441
+ context: context,
398
442
  )
399
443
  end
400
444
 
@@ -412,6 +456,15 @@ module Mongo
412
456
  def resuming?
413
457
  !!@resuming
414
458
  end
459
+
460
+ # Recreates the current cursor (typically as a consequence of attempting
461
+ # to resume the change stream)
462
+ def recreate_cursor!(context = nil)
463
+ @timed_out = false
464
+
465
+ close
466
+ create_cursor!(context&.remaining_timeout_ms)
467
+ end
415
468
  end
416
469
  end
417
470
  end