mongo 2.20.1 → 2.21.0

Sign up to get free protection for your applications and to get access to all the features.
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