mongo 2.9.2 → 2.10.0.rc0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (227) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data.tar.gz.sig +0 -0
  4. data/lib/mongo.rb +1 -0
  5. data/lib/mongo/auth/user/view.rb +4 -4
  6. data/lib/mongo/bulk_write.rb +14 -8
  7. data/lib/mongo/bulk_write/result.rb +1 -1
  8. data/lib/mongo/bulk_write/result_combiner.rb +2 -2
  9. data/lib/mongo/bulk_write/transformable.rb +17 -9
  10. data/lib/mongo/client.rb +107 -16
  11. data/lib/mongo/cluster.rb +47 -25
  12. data/lib/mongo/cluster/topology/replica_set_no_primary.rb +1 -1
  13. data/lib/mongo/cluster_time.rb +139 -0
  14. data/lib/mongo/collection.rb +84 -25
  15. data/lib/mongo/collection/view.rb +7 -3
  16. data/lib/mongo/collection/view/aggregation.rb +4 -4
  17. data/lib/mongo/collection/view/builder/aggregation.rb +31 -6
  18. data/lib/mongo/collection/view/builder/find_command.rb +4 -1
  19. data/lib/mongo/collection/view/builder/map_reduce.rb +4 -1
  20. data/lib/mongo/collection/view/change_stream.rb +54 -66
  21. data/lib/mongo/collection/view/iterable.rb +2 -2
  22. data/lib/mongo/collection/view/map_reduce.rb +6 -4
  23. data/lib/mongo/collection/view/readable.rb +36 -16
  24. data/lib/mongo/collection/view/writable.rb +68 -22
  25. data/lib/mongo/cursor.rb +87 -20
  26. data/lib/mongo/database.rb +47 -43
  27. data/lib/mongo/database/view.rb +54 -11
  28. data/lib/mongo/error.rb +13 -4
  29. data/lib/mongo/error/invalid_write_concern.rb +2 -2
  30. data/lib/mongo/error/operation_failure.rb +65 -11
  31. data/lib/mongo/error/parser.rb +41 -8
  32. data/lib/mongo/grid/fs_bucket.rb +26 -6
  33. data/lib/mongo/grid/stream/read.rb +9 -2
  34. data/lib/mongo/grid/stream/write.rb +21 -5
  35. data/lib/mongo/index/view.rb +3 -3
  36. data/lib/mongo/lint.rb +10 -3
  37. data/lib/mongo/operation.rb +2 -0
  38. data/lib/mongo/operation/aggregate/result.rb +19 -6
  39. data/lib/mongo/operation/collections_info.rb +1 -1
  40. data/lib/mongo/operation/get_more/result.rb +9 -0
  41. data/lib/mongo/operation/list_collections/command.rb +1 -3
  42. data/lib/mongo/operation/list_collections/op_msg.rb +1 -2
  43. data/lib/mongo/operation/parallel_scan/command.rb +4 -1
  44. data/lib/mongo/operation/parallel_scan/op_msg.rb +4 -1
  45. data/lib/mongo/operation/result.rb +27 -4
  46. data/lib/mongo/operation/shared/executable.rb +19 -5
  47. data/lib/mongo/operation/shared/executable_no_validate.rb +1 -2
  48. data/lib/mongo/operation/shared/executable_transaction_label.rb +0 -9
  49. data/lib/mongo/operation/shared/polymorphic_result.rb +9 -1
  50. data/lib/mongo/operation/shared/result/aggregatable.rb +2 -2
  51. data/lib/mongo/operation/shared/sessions_supported.rb +42 -32
  52. data/lib/mongo/operation/shared/specifiable.rb +40 -0
  53. data/lib/mongo/operation/shared/unpinnable.rb +39 -0
  54. data/lib/mongo/operation/shared/write.rb +1 -1
  55. data/lib/mongo/protocol/update.rb +6 -2
  56. data/lib/mongo/retryable.rb +79 -39
  57. data/lib/mongo/server/connection.rb +10 -3
  58. data/lib/mongo/server/description.rb +25 -1
  59. data/lib/mongo/server/monitor/connection.rb +1 -1
  60. data/lib/mongo/server_selector.rb +10 -0
  61. data/lib/mongo/server_selector/selectable.rb +172 -32
  62. data/lib/mongo/session.rb +654 -581
  63. data/lib/mongo/session/session_pool.rb +1 -1
  64. data/lib/mongo/socket.rb +7 -28
  65. data/lib/mongo/socket/ssl.rb +26 -1
  66. data/lib/mongo/socket/tcp.rb +3 -0
  67. data/lib/mongo/socket/unix.rb +3 -0
  68. data/lib/mongo/uri.rb +112 -265
  69. data/lib/mongo/uri/srv_protocol.rb +4 -1
  70. data/lib/mongo/version.rb +1 -1
  71. data/lib/mongo/write_concern.rb +10 -29
  72. data/lib/mongo/write_concern/acknowledged.rb +12 -0
  73. data/lib/mongo/write_concern/base.rb +17 -13
  74. data/lib/mongo/write_concern/unacknowledged.rb +12 -0
  75. data/spec/atlas/atlas_connectivity_spec.rb +7 -37
  76. data/spec/atlas/operations_spec.rb +25 -0
  77. data/spec/integration/change_stream_examples_spec.rb +45 -31
  78. data/spec/integration/change_stream_spec.rb +305 -5
  79. data/spec/integration/client_spec.rb +44 -0
  80. data/spec/integration/command_monitoring_spec.rb +1 -0
  81. data/spec/integration/command_spec.rb +7 -1
  82. data/spec/integration/mmapv1_spec.rb +28 -0
  83. data/spec/integration/mongos_pinning_spec.rb +34 -0
  84. data/spec/integration/operation_failure_code_spec.rb +2 -2
  85. data/spec/integration/{read_concern.rb → read_concern_spec.rb} +7 -1
  86. data/spec/integration/read_preference_spec.rb +485 -0
  87. data/spec/integration/retryable_writes_spec.rb +8 -19
  88. data/spec/integration/sdam_error_handling_spec.rb +1 -1
  89. data/spec/integration/sdam_events_spec.rb +2 -2
  90. data/spec/integration/server_description_spec.rb +14 -17
  91. data/spec/integration/server_selector_spec.rb +7 -3
  92. data/spec/integration/server_spec.rb +48 -0
  93. data/spec/integration/ssl_uri_options_spec.rb +1 -1
  94. data/spec/integration/step_down_spec.rb +10 -4
  95. data/spec/integration/transactions_examples_spec.rb +11 -10
  96. data/spec/lite_spec_helper.rb +19 -16
  97. data/spec/mongo/auth/scram/negotiation_spec.rb +11 -8
  98. data/spec/mongo/bulk_write/ordered_combiner_spec.rb +6 -6
  99. data/spec/mongo/bulk_write/unordered_combiner_spec.rb +4 -4
  100. data/spec/mongo/bulk_write_spec.rb +12 -2
  101. data/spec/mongo/client_construction_spec.rb +160 -8
  102. data/spec/mongo/client_spec.rb +5 -4
  103. data/spec/mongo/cluster_spec.rb +6 -6
  104. data/spec/mongo/cluster_time_spec.rb +148 -0
  105. data/spec/mongo/collection/view/aggregation_spec.rb +34 -15
  106. data/spec/mongo/collection/view/change_stream_spec.rb +62 -3
  107. data/spec/mongo/collection/view/map_reduce_spec.rb +7 -5
  108. data/spec/mongo/collection/view/readable_spec.rb +4 -4
  109. data/spec/mongo/collection_spec.rb +331 -14
  110. data/spec/mongo/cursor_spec.rb +117 -5
  111. data/spec/mongo/database_spec.rb +240 -8
  112. data/spec/mongo/error/operation_failure_spec.rb +47 -1
  113. data/spec/mongo/error/parser_spec.rb +160 -23
  114. data/spec/mongo/operation/insert/bulk_spec.rb +2 -1
  115. data/spec/mongo/operation/result_spec.rb +27 -0
  116. data/spec/mongo/operation/update/bulk_spec.rb +1 -0
  117. data/spec/mongo/retryable_spec.rb +2 -0
  118. data/spec/mongo/server/app_metadata_spec.rb +2 -2
  119. data/spec/mongo/server/connection_spec.rb +13 -17
  120. data/spec/mongo/server/monitor/connection_spec.rb +13 -10
  121. data/spec/mongo/server_selector_spec.rb +34 -2
  122. data/spec/mongo/session/session_pool_spec.rb +14 -3
  123. data/spec/mongo/session_spec.rb +3 -3
  124. data/spec/mongo/session_transaction_spec.rb +4 -3
  125. data/spec/mongo/socket/ssl_spec.rb +19 -5
  126. data/spec/mongo/socket_spec.rb +1 -62
  127. data/spec/mongo/uri/srv_protocol_spec.rb +14 -20
  128. data/spec/mongo/uri_option_parsing_spec.rb +94 -8
  129. data/spec/mongo/uri_spec.rb +23 -10
  130. data/spec/mongo/write_concern_spec.rb +56 -3
  131. data/spec/spec_tests/change_streams_spec.rb +2 -1
  132. data/spec/spec_tests/cmap_spec.rb +1 -1
  133. data/spec/spec_tests/crud_spec.rb +12 -2
  134. data/spec/spec_tests/data/change_streams/change-streams-errors.yml +24 -1
  135. data/spec/spec_tests/data/change_streams/change-streams.yml +172 -3
  136. data/spec/spec_tests/data/command_monitoring/bulkWrite.yml +1 -1
  137. data/spec/spec_tests/data/command_monitoring/updateMany.yml +0 -2
  138. data/spec/spec_tests/data/command_monitoring/updateOne.yml +0 -5
  139. data/spec/spec_tests/data/crud/read/aggregate-out.yml +0 -6
  140. data/spec/spec_tests/data/crud/read/count-empty.yml +29 -0
  141. data/spec/spec_tests/data/crud/write/bulkWrite-arrayFilters.yml +1 -0
  142. data/spec/spec_tests/data/crud/write/bulkWrite-collation.yml +101 -0
  143. data/spec/spec_tests/data/crud/write/bulkWrite.yml +401 -0
  144. data/spec/spec_tests/data/crud/write/insertMany.yml +58 -2
  145. data/spec/spec_tests/data/crud/write/updateMany-arrayFilters.yml +3 -0
  146. data/spec/spec_tests/data/crud/write/updateOne-arrayFilters.yml +6 -1
  147. data/spec/spec_tests/data/crud_v2/aggregate-merge.yml +103 -0
  148. data/spec/spec_tests/data/crud_v2/aggregate-out-readConcern.yml +110 -0
  149. data/spec/spec_tests/data/crud_v2/bulkWrite-arrayFilters.yml +81 -0
  150. data/spec/spec_tests/data/crud_v2/db-aggregate.yml +38 -0
  151. data/spec/spec_tests/data/crud_v2/updateWithPipelines.yml +92 -0
  152. data/spec/spec_tests/data/retryable_writes/insertOne-serverErrors.yml +2 -2
  153. data/spec/spec_tests/data/transactions/abort.yml +3 -0
  154. data/spec/spec_tests/data/transactions/bulk.yml +3 -8
  155. data/spec/spec_tests/data/transactions/causal-consistency.yml +3 -8
  156. data/spec/spec_tests/data/transactions/commit.yml +3 -1
  157. data/spec/spec_tests/data/transactions/count.yml +3 -0
  158. data/spec/spec_tests/data/transactions/delete.yml +3 -0
  159. data/spec/spec_tests/data/transactions/error-labels.yml +4 -1
  160. data/spec/spec_tests/data/transactions/errors-client.yml +56 -0
  161. data/spec/spec_tests/data/transactions/errors.yml +3 -0
  162. data/spec/spec_tests/data/transactions/findOneAndDelete.yml +3 -0
  163. data/spec/spec_tests/data/transactions/findOneAndReplace.yml +3 -0
  164. data/spec/spec_tests/data/transactions/findOneAndUpdate.yml +3 -0
  165. data/spec/spec_tests/data/transactions/insert.yml +3 -0
  166. data/spec/spec_tests/data/transactions/isolation.yml +3 -0
  167. data/spec/spec_tests/data/transactions/mongos-pin-auto.yml +1671 -0
  168. data/spec/spec_tests/data/transactions/mongos-recovery-token.yml +347 -0
  169. data/spec/spec_tests/data/transactions/pin-mongos.yml +557 -0
  170. data/spec/spec_tests/data/transactions/read-concern.yml +3 -0
  171. data/spec/spec_tests/data/transactions/read-pref.yml +3 -0
  172. data/spec/spec_tests/data/transactions/reads.yml +3 -0
  173. data/spec/spec_tests/data/transactions/retryable-abort.yml +5 -2
  174. data/spec/spec_tests/data/transactions/retryable-commit.yml +4 -1
  175. data/spec/spec_tests/data/transactions/retryable-writes.yml +3 -0
  176. data/spec/spec_tests/data/transactions/run-command.yml +3 -0
  177. data/spec/spec_tests/data/transactions/transaction-options.yml +6 -0
  178. data/spec/spec_tests/data/transactions/update.yml +3 -8
  179. data/spec/spec_tests/data/transactions/write-concern.yml +348 -38
  180. data/spec/spec_tests/data/transactions_api/callback-aborts.yml +6 -0
  181. data/spec/spec_tests/data/transactions_api/callback-commits.yml +5 -0
  182. data/spec/spec_tests/data/transactions_api/callback-retry.yml +7 -2
  183. data/spec/spec_tests/data/transactions_api/commit-retry.yml +70 -15
  184. data/spec/spec_tests/data/transactions_api/commit-transienttransactionerror-4.2.yml +3 -0
  185. data/spec/spec_tests/data/transactions_api/commit-transienttransactionerror.yml +3 -0
  186. data/spec/spec_tests/data/transactions_api/commit-writeconcernerror.yml +59 -109
  187. data/spec/spec_tests/data/transactions_api/commit.yml +5 -0
  188. data/spec/spec_tests/data/transactions_api/transaction-options.yml +10 -0
  189. data/spec/spec_tests/retryable_reads_spec.rb +5 -2
  190. data/spec/spec_tests/retryable_writes_spec.rb +5 -2
  191. data/spec/spec_tests/sdam_monitoring_spec.rb +3 -3
  192. data/spec/spec_tests/sdam_spec.rb +2 -2
  193. data/spec/spec_tests/transactions_api_spec.rb +1 -67
  194. data/spec/spec_tests/transactions_spec.rb +2 -66
  195. data/spec/support/authorization.rb +4 -0
  196. data/spec/support/change_streams.rb +30 -10
  197. data/spec/support/change_streams/operation.rb +27 -0
  198. data/spec/support/client_registry.rb +44 -25
  199. data/spec/support/cluster_config.rb +25 -14
  200. data/spec/support/cluster_tools.rb +32 -10
  201. data/spec/support/command_monitoring.rb +1 -1
  202. data/spec/support/common_shortcuts.rb +30 -0
  203. data/spec/support/connection_string.rb +8 -3
  204. data/spec/support/constraints.rb +34 -0
  205. data/spec/support/crud.rb +31 -16
  206. data/spec/support/crud/context.rb +23 -0
  207. data/spec/support/crud/operation.rb +311 -14
  208. data/spec/support/crud/spec.rb +2 -1
  209. data/spec/support/crud/test.rb +24 -27
  210. data/spec/support/crud/test_base.rb +22 -0
  211. data/spec/support/crud/verifier.rb +15 -1
  212. data/spec/support/event_subscriber.rb +12 -0
  213. data/spec/support/sdam_formatter_integration.rb +12 -6
  214. data/spec/support/shared/server_selector.rb +10 -0
  215. data/spec/support/shared/session.rb +13 -12
  216. data/spec/support/spec_config.rb +32 -22
  217. data/spec/support/spec_setup.rb +2 -2
  218. data/spec/support/transactions.rb +87 -0
  219. data/spec/support/transactions/context.rb +33 -0
  220. data/spec/support/transactions/operation.rb +99 -349
  221. data/spec/support/transactions/spec.rb +1 -3
  222. data/spec/support/transactions/test.rb +110 -49
  223. data/spec/support/utils.rb +74 -1
  224. metadata +52 -10
  225. metadata.gz.sig +0 -0
  226. data/spec/support/crud/read.rb +0 -265
  227. data/spec/support/crud/write.rb +0 -284
@@ -46,7 +46,6 @@ module Mongo
46
46
  include Immutable
47
47
  include Iterable
48
48
  include Readable
49
- include Retryable
50
49
  include Explainable
51
50
  include Writable
52
51
 
@@ -60,7 +59,12 @@ module Mongo
60
59
  def_delegators :collection,
61
60
  :client,
62
61
  :cluster,
63
- :database
62
+ :database,
63
+ :read_with_retry,
64
+ :read_with_retry_cursor,
65
+ :write_with_retry,
66
+ :nro_write_with_retry,
67
+ :write_concern_with_session
64
68
 
65
69
  # Delegate to the cluster for the next primary.
66
70
  def_delegators :cluster, :next_primary
@@ -163,7 +167,7 @@ module Mongo
163
167
  #
164
168
  # @since 2.0.0
165
169
  def write_concern
166
- WriteConcern.get(options[:write] || options[:write_concern] || collection.write_concern)
170
+ WriteConcern.get(options[:write_concern] || options[:write] || collection.write_concern)
167
171
  end
168
172
 
169
173
  private
@@ -111,18 +111,18 @@ module Mongo
111
111
  server.standalone? || server.mongos? || server.primary? || secondary_ok?
112
112
  end
113
113
 
114
- def out?
115
- pipeline.any? { |op| op.key?('$out') || op.key?(:$out) }
114
+ def write?
115
+ pipeline.any? { |op| op.key?('$out') || op.key?(:$out) || op.key?('$merge') || op.key?(:$merge) }
116
116
  end
117
117
 
118
118
  def secondary_ok?
119
- !out?
119
+ !write?
120
120
  end
121
121
 
122
122
  def send_initial_query(server, session)
123
123
  unless valid_server?(server)
124
124
  log_warn("Rerouting the Aggregation operation to the primary server - #{server.summary} is not suitable")
125
- server = cluster.next_primary
125
+ server = cluster.next_primary(nil, session)
126
126
  end
127
127
  validate_collation!(server)
128
128
  initial_query_op(session).execute(server)
@@ -78,22 +78,40 @@ module Mongo
78
78
  spec = {
79
79
  selector: aggregation_command,
80
80
  db_name: database.name,
81
- read: read,
81
+ read: view.read_preference,
82
82
  session: @options[:session]
83
83
  }
84
- write? ? spec.merge!(write_concern: write_concern) : spec
84
+ if write?
85
+ spec.update(write_concern: write_concern)
86
+ end
87
+ spec
85
88
  end
86
89
 
87
90
  private
88
91
 
89
92
  def write?
90
- pipeline.any? { |operator| operator[:$out] || operator['$out'] }
93
+ pipeline.any? do |operator|
94
+ operator[:$out] || operator['$out'] ||
95
+ operator[:$merge] || operator['$merge']
96
+ end
91
97
  end
92
98
 
93
99
  def aggregation_command
94
- command = BSON::Document.new(:aggregate => collection.name, :pipeline => pipeline)
100
+ command = BSON::Document.new
101
+ # aggregate must be the first key in the command document
102
+ if view.is_a?(Collection::View)
103
+ command[:aggregate] = collection.name
104
+ elsif view.is_a?(Database::View)
105
+ command[:aggregate] = 1
106
+ else
107
+ raise ArgumentError, "Unknown view class: #{view}"
108
+ end
109
+ command[:pipeline] = pipeline
110
+ if read_concern = view.read_concern
111
+ command[:readConcern] = Options::Mapper.transform_values_to_strings(
112
+ read_concern)
113
+ end
95
114
  command[:cursor] = cursor if cursor
96
- command[:readConcern] = collection.read_concern if collection.read_concern
97
115
  command.merge!(Options::Mapper.transform_documents(options, MAPPINGS))
98
116
  command
99
117
  end
@@ -105,7 +123,14 @@ module Mongo
105
123
  end
106
124
 
107
125
  def batch_size_doc
108
- (value = options[:batch_size] || view.batch_size) ? { :batchSize => value } : {}
126
+ value = options[:batch_size] || view.batch_size
127
+ if value == 0 && write?
128
+ {}
129
+ elsif value
130
+ { :batchSize => value }
131
+ else
132
+ {}
133
+ end
109
134
  end
110
135
  end
111
136
  end
@@ -96,7 +96,10 @@ module Mongo
96
96
 
97
97
  def find_command
98
98
  document = BSON::Document.new('find' => collection.name, 'filter' => filter)
99
- document[:readConcern] = collection.read_concern if collection.read_concern
99
+ if collection.read_concern
100
+ document[:readConcern] = Options::Mapper.transform_values_to_strings(
101
+ collection.read_concern)
102
+ end
100
103
  command = Options::Mapper.transform_documents(convert_flags(options), MAPPINGS, document)
101
104
  convert_limit_and_batch_size(command)
102
105
  command
@@ -139,7 +139,10 @@ module Mongo
139
139
  :query => filter,
140
140
  :out => { inline: 1 }
141
141
  )
142
- command[:readConcern] = collection.read_concern if collection.read_concern
142
+ if collection.read_concern
143
+ command[:readConcern] = Options::Mapper.transform_values_to_strings(
144
+ collection.read_concern)
145
+ end
143
146
  command.merge!(view_options)
144
147
  command.merge!(Options::Mapper.transform_documents(options, MAPPINGS))
145
148
  command
@@ -94,8 +94,12 @@ module Mongo
94
94
  @changes_for = changes_for
95
95
  @change_stream_filters = pipeline && pipeline.dup
96
96
  @options = options && options.dup.freeze
97
- @resume_token = @options[:resume_after]
98
97
  @start_after = @options[:start_after]
98
+
99
+ # The resume token tracked by the change stream, used only
100
+ # when there is no cursor, or no cursor resume token
101
+ @resume_token = @start_after || @options[:resume_after]
102
+
99
103
  create_cursor!
100
104
 
101
105
  # We send different parameters when we resume a change stream
@@ -121,26 +125,12 @@ module Mongo
121
125
  # @yieldparam [ BSON::Document ] Each change stream document.
122
126
  def each
123
127
  raise StopIteration.new if closed?
124
- retried = false
125
- begin
126
- @cursor.each do |doc|
127
- cache_resume_token(doc)
128
- yield doc
129
- end if block_given?
130
- @cursor.to_enum
131
- rescue Mongo::Error => e
132
- if retried || !e.change_stream_resumable?
133
- raise
134
- end
135
-
136
- retried = true
137
- # Rerun initial aggregation.
138
- # Any errors here will stop iteration and break out of this
139
- # method
140
- close
141
- create_cursor!
142
- retry
128
+ loop do
129
+ document = try_next
130
+ yield document if document
143
131
  end
132
+ rescue StopIteration => e
133
+ return self
144
134
  end
145
135
 
146
136
  # Return one document from the change stream, if one is available.
@@ -153,10 +143,7 @@ module Mongo
153
143
  # for changes from the server, and if no changes are received
154
144
  # it will return nil.
155
145
  #
156
- # @note This method is experimental and subject to change.
157
- #
158
146
  # @return [ BSON::Document | nil ] A change stream document.
159
- # @api experimental
160
147
  # @since 2.6.0
161
148
  def try_next
162
149
  raise StopIteration.new if closed?
@@ -165,27 +152,28 @@ module Mongo
165
152
  begin
166
153
  doc = @cursor.try_next
167
154
  rescue Mongo::Error => e
168
- unless e.change_stream_resumable?
155
+ if retried || !e.change_stream_resumable?
169
156
  raise
170
157
  end
171
158
 
172
- if retried
173
- # Rerun initial aggregation.
174
- # Any errors here will stop iteration and break out of this
175
- # method
176
- close
177
- create_cursor!
178
- retried = false
179
- doc = @cursor.try_next
180
- else
181
- # Attempt to retry a getMore once
182
- retried = true
183
- retry
184
- end
159
+ retried = true
160
+ # Rerun initial aggregation.
161
+ # Any errors here will stop iteration and break out of this
162
+ # method
163
+
164
+ # Save cursor's resume token so we can use it
165
+ # to create a new cursor
166
+ @resume_token = @cursor.resume_token
167
+
168
+ close
169
+ create_cursor!
170
+ retry
185
171
  end
186
172
 
187
- if doc
188
- cache_resume_token(doc)
173
+ # We need to verify each doc has an _id, so we
174
+ # have a resume token to work with
175
+ if doc && doc['_id'].nil?
176
+ raise Error::MissingResumeToken
189
177
  end
190
178
  doc
191
179
  end
@@ -238,7 +226,21 @@ module Mongo
238
226
  # @since 2.5.0
239
227
  def inspect
240
228
  "#<Mongo::Collection::View:ChangeStream:0x#{object_id} filters=#{@change_stream_filters} " +
241
- "options=#{@options} resume_token=#{@resume_token}>"
229
+ "options=#{@options} resume_token=#{resume_token}>"
230
+ end
231
+
232
+ # Returns the resume token that the stream will
233
+ # use to automatically resume, if one exists.
234
+ #
235
+ # @example Get the change stream resume token.
236
+ # stream.resume_token
237
+ #
238
+ # @return [ BSON::Document | nil ] The change stream resume token.
239
+ #
240
+ # @since 2.10.0
241
+ def resume_token
242
+ cursor_resume_token = @cursor.resume_token if @cursor
243
+ cursor_resume_token || @resume_token
242
244
  end
243
245
 
244
246
  private
@@ -255,15 +257,6 @@ module Mongo
255
257
  !for_cluster? && !for_database?
256
258
  end
257
259
 
258
- def cache_resume_token(doc)
259
- # Always record both resume token and operation time,
260
- # in case we get an older or newer server during rolling
261
- # upgrades/downgrades
262
- unless @resume_token = (doc[:_id] && doc[:_id].dup)
263
- raise Error::MissingResumeToken
264
- end
265
- end
266
-
267
260
  def create_cursor!
268
261
  # clear the cache because we may get a newer or an older server
269
262
  # (rolling upgrades)
@@ -271,7 +264,10 @@ module Mongo
271
264
 
272
265
  session = client.send(:get_session, @options)
273
266
  start_at_operation_time = nil
267
+ start_at_operation_time_supported = nil
274
268
  @cursor = read_with_retry_cursor(session, server_selector, view) do |server|
269
+ start_at_operation_time_supported = server.description.server_version_gte?('4.0')
270
+
275
271
  result = send_initial_query(server, session)
276
272
  if doc = result.replies.first && result.replies.first.documents.first
277
273
  start_at_operation_time = doc['operationTime']
@@ -286,6 +282,7 @@ module Mongo
286
282
  result
287
283
  end
288
284
  @start_at_operation_time = start_at_operation_time
285
+ @start_at_operation_time_supported = start_at_operation_time_supported
289
286
  end
290
287
 
291
288
  def pipeline
@@ -305,14 +302,11 @@ module Mongo
305
302
  # However, if the first getMore fails and the user didn't pass
306
303
  # a resume token we won't have a resume token to use.
307
304
  # Use start_at_operation time in this case
308
- if @resume_token
309
- # Spec says we need to remove startAtOperationTime if
310
- # one was passed in by user, thus we won't forward it
311
- elsif @start_after
312
- # The spec says to set `resumeAfter` to the `startAfter` token and not to send
313
- # either `startAfter` or `startAtOperationTime`.
314
- @resume_token = @start_after
315
- elsif start_at_operation_time_supported? && @start_at_operation_time
305
+ if resume_token
306
+ # Spec says we need to remove both startAtOperationTime and startAfter if
307
+ # either was passed in by user, thus we won't forward them
308
+ doc[:resumeAfter] = resume_token
309
+ elsif @start_at_operation_time_supported && @start_at_operation_time
316
310
  # It is crucial to check @start_at_operation_time_supported
317
311
  # here - we may have switched to an older server that
318
312
  # does not support operation times and therefore shouldn't
@@ -327,6 +321,8 @@ module Mongo
327
321
  else
328
322
  if @start_after
329
323
  doc[:startAfter] = @start_after
324
+ elsif resume_token
325
+ doc[:resumeAfter] = resume_token
330
326
  end
331
327
 
332
328
  if options[:start_at_operation_time]
@@ -334,7 +330,7 @@ module Mongo
334
330
  options[:start_at_operation_time])
335
331
  end
336
332
  end
337
- doc[:resumeAfter] = @resume_token if @resume_token
333
+
338
334
  doc[:allChangesForCluster] = true if for_cluster?
339
335
  end
340
336
  end
@@ -357,14 +353,6 @@ module Mongo
357
353
  def resuming?
358
354
  !!@resuming
359
355
  end
360
-
361
- def start_at_operation_time_supported?
362
- if @start_at_operation_time_supported.nil?
363
- server = server_selector.select_server(cluster)
364
- @start_at_operation_time_supported = server.description.max_wire_version >= 7
365
- end
366
- @start_at_operation_time_supported
367
- end
368
356
  end
369
357
  end
370
358
  end
@@ -37,8 +37,8 @@ module Mongo
37
37
  def each
38
38
  @cursor = nil
39
39
  session = client.send(:get_session, @options)
40
- @cursor = if respond_to?(:out?, true) && out?
41
- server = server_selector.select_server(cluster)
40
+ @cursor = if respond_to?(:write?, true) && write?
41
+ server = server_selector.select_server(cluster, nil, session)
42
42
  result = send_initial_query(server, session)
43
43
  Cursor.new(view, result, server, session: session)
44
44
  else
@@ -68,7 +68,7 @@ module Mongo
68
68
  def each
69
69
  @cursor = nil
70
70
  session = client.send(:get_session, @options)
71
- server = cluster.next_primary
71
+ server = cluster.next_primary(nil, session)
72
72
  result = send_initial_query(server, session)
73
73
  result = send_fetch_query(server, session) unless inline?
74
74
  @cursor = Cursor.new(view, result, server, session: session)
@@ -195,7 +195,8 @@ module Mongo
195
195
  # @since 2.5.0
196
196
  def execute
197
197
  view.send(:with_session, @options) do |session|
198
- legacy_write_with_retry do |server|
198
+ write_concern = view.write_concern_with_session(session)
199
+ nro_write_with_retry(session, write_concern) do |server|
199
200
  send_initial_query(server, session)
200
201
  end
201
202
  end
@@ -233,8 +234,9 @@ module Mongo
233
234
 
234
235
  def send_initial_query(server, session)
235
236
  unless valid_server?(server)
236
- log_warn("Rerouting the MapReduce operation to the primary server - #{server.summary} is not suitable")
237
- server = cluster.next_primary
237
+ msg = "Rerouting the MapReduce operation to the primary server - #{server.summary} is not suitable"
238
+ log_warn(msg)
239
+ server = cluster.next_primary(nil, session)
238
240
  end
239
241
  validate_collation!(server)
240
242
  initial_query_op(session).execute(server)
@@ -138,7 +138,10 @@ module Mongo
138
138
  cmd[:skip] = opts[:skip] if opts[:skip]
139
139
  cmd[:hint] = opts[:hint] if opts[:hint]
140
140
  cmd[:limit] = opts[:limit] if opts[:limit]
141
- cmd[:readConcern] = read_concern if read_concern
141
+ if read_concern
142
+ cmd[:readConcern] = Options::Mapper.transform_values_to_strings(
143
+ read_concern)
144
+ end
142
145
  cmd[:maxTimeMS] = opts[:max_time_ms] if opts[:max_time_ms]
143
146
  Mongo::Lint.validate_underscore_read_preference(opts[:read])
144
147
  read_pref = opts[:read] || read_preference
@@ -205,7 +208,10 @@ module Mongo
205
208
  def estimated_document_count(opts = {})
206
209
  cmd = { count: collection.name }
207
210
  cmd[:maxTimeMS] = opts[:max_time_ms] if opts[:max_time_ms]
208
- cmd[:readConcern] = read_concern if read_concern
211
+ if read_concern
212
+ cmd[:readConcern] = Options::Mapper.transform_values_to_strings(
213
+ read_concern)
214
+ end
209
215
  Mongo::Lint.validate_underscore_read_preference(opts[:read])
210
216
  read_pref = opts[:read] || read_preference
211
217
  selector = ServerSelector.get(read_pref || server_selector)
@@ -242,7 +248,10 @@ module Mongo
242
248
  :key => field_name.to_s,
243
249
  :query => filter }
244
250
  cmd[:maxTimeMS] = opts[:max_time_ms] if opts[:max_time_ms]
245
- cmd[:readConcern] = read_concern if read_concern
251
+ if read_concern
252
+ cmd[:readConcern] = Options::Mapper.transform_values_to_strings(
253
+ read_concern)
254
+ end
246
255
  Mongo::Lint.validate_underscore_read_preference(opts[:read])
247
256
  read_pref = opts[:read] || read_preference
248
257
  selector = ServerSelector.get(read_pref || server_selector)
@@ -533,12 +542,7 @@ module Mongo
533
542
  configure(:cursor_type, type)
534
543
  end
535
544
 
536
- private
537
-
538
- def collation(doc = nil)
539
- configure(:collation, doc)
540
- end
541
-
545
+ # @api private
542
546
  def read_concern
543
547
  if options[:session] && options[:session].in_transaction?
544
548
  options[:session].send(:txn_read_concern) || collection.client.read_concern
@@ -547,14 +551,30 @@ module Mongo
547
551
  end
548
552
  end
549
553
 
554
+ # @api private
550
555
  def read_preference
551
- rp = if options[:session] && options[:session].in_transaction?
552
- options[:session].txn_read_preference || collection.client.read_preference
553
- else
554
- @read_preference ||= (options[:read] || collection.read_preference)
556
+ @read_preference ||= begin
557
+ # Operation read preference is always respected, and has the
558
+ # highest priority. If we are in a transaction, we look at
559
+ # transaction read preference and default to client, ignoring
560
+ # collection read preference. If we are not in transaction we
561
+ # look at collection read preference which defaults to client.
562
+ rp = if options[:read]
563
+ options[:read]
564
+ elsif options[:session] && options[:session].in_transaction?
565
+ options[:session].txn_read_preference || collection.client.read_preference
566
+ else
567
+ collection.read_preference
568
+ end
569
+ Lint.validate_underscore_read_preference(rp)
570
+ rp
555
571
  end
556
- Lint.validate_underscore_read_preference(rp)
557
- rp
572
+ end
573
+
574
+ private
575
+
576
+ def collation(doc = nil)
577
+ configure(:collation, doc)
558
578
  end
559
579
 
560
580
  def server_selector
@@ -571,7 +591,7 @@ module Mongo
571
591
  else
572
592
  session = nil
573
593
  end
574
- server = server_selector.select_server(cluster)
594
+ server = server_selector.select_server(cluster, nil, session)
575
595
  cmd = Operation::ParallelScan.new({
576
596
  :coll_name => collection.name,
577
597
  :db_name => database.name,