mongo 2.4.3 → 2.5.0.beta

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 (235) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +3 -2
  3. data.tar.gz.sig +0 -0
  4. data/lib/mongo.rb +3 -2
  5. data/lib/mongo/auth/cr.rb +6 -4
  6. data/lib/mongo/auth/cr/conversation.rb +33 -17
  7. data/lib/mongo/auth/ldap.rb +4 -2
  8. data/lib/mongo/auth/ldap/conversation.rb +19 -9
  9. data/lib/mongo/auth/scram.rb +7 -4
  10. data/lib/mongo/auth/scram/conversation.rb +62 -24
  11. data/lib/mongo/auth/user.rb +10 -0
  12. data/lib/mongo/auth/user/view.rb +44 -22
  13. data/lib/mongo/auth/x509.rb +4 -2
  14. data/lib/mongo/auth/x509/conversation.rb +19 -9
  15. data/lib/mongo/bulk_write.rb +33 -27
  16. data/lib/mongo/bulk_write/combineable.rb +5 -0
  17. data/lib/mongo/bulk_write/transformable.rb +2 -0
  18. data/lib/mongo/bulk_write/validatable.rb +4 -0
  19. data/lib/mongo/client.rb +123 -12
  20. data/lib/mongo/cluster.rb +52 -11
  21. data/lib/mongo/cluster/app_metadata.rb +8 -2
  22. data/lib/mongo/cluster/cursor_reaper.rb +0 -1
  23. data/lib/mongo/cluster/topology.rb +1 -1
  24. data/lib/mongo/collection.rb +114 -27
  25. data/lib/mongo/collection/view.rb +8 -2
  26. data/lib/mongo/collection/view/aggregation.rb +11 -7
  27. data/lib/mongo/collection/view/builder/aggregation.rb +5 -1
  28. data/lib/mongo/collection/view/builder/find_command.rb +5 -3
  29. data/lib/mongo/collection/view/builder/map_reduce.rb +11 -3
  30. data/lib/mongo/collection/view/builder/op_query.rb +1 -1
  31. data/lib/mongo/collection/view/change_stream.rb +160 -0
  32. data/lib/mongo/collection/view/change_stream/retryable.rb +57 -0
  33. data/lib/mongo/collection/view/iterable.rb +11 -10
  34. data/lib/mongo/collection/view/map_reduce.rb +22 -18
  35. data/lib/mongo/collection/view/readable.rb +51 -37
  36. data/lib/mongo/collection/view/writable.rb +72 -40
  37. data/lib/mongo/cursor.rb +25 -4
  38. data/lib/mongo/cursor/builder/get_more_command.rb +4 -2
  39. data/lib/mongo/database.rb +22 -11
  40. data/lib/mongo/database/view.rb +16 -12
  41. data/lib/mongo/error.rb +5 -0
  42. data/lib/mongo/error/invalid_session.rb +36 -0
  43. data/lib/mongo/error/missing_resume_token.rb +39 -0
  44. data/lib/mongo/error/operation_failure.rb +17 -0
  45. data/lib/mongo/error/parser.rb +3 -2
  46. data/lib/mongo/error/unknown_payload_type.rb +41 -0
  47. data/lib/mongo/error/unsupported_array_filters.rb +51 -0
  48. data/lib/mongo/error/unsupported_message_type.rb +23 -0
  49. data/lib/mongo/grid/fs_bucket.rb +5 -4
  50. data/lib/mongo/grid/stream/read.rb +3 -2
  51. data/lib/mongo/grid/stream/write.rb +2 -2
  52. data/lib/mongo/index/view.rb +35 -25
  53. data/lib/mongo/monitoring/event/secure.rb +14 -0
  54. data/lib/mongo/operation.rb +16 -0
  55. data/lib/mongo/operation/commands.rb +1 -0
  56. data/lib/mongo/operation/commands/aggregate.rb +9 -5
  57. data/lib/mongo/operation/commands/aggregate/result.rb +1 -1
  58. data/lib/mongo/operation/commands/collections_info.rb +6 -6
  59. data/lib/mongo/operation/commands/command.rb +2 -1
  60. data/lib/mongo/operation/commands/create.rb +6 -2
  61. data/lib/mongo/operation/commands/drop.rb +6 -2
  62. data/lib/mongo/operation/commands/drop_database.rb +6 -2
  63. data/lib/mongo/operation/commands/explain.rb +27 -0
  64. data/lib/mongo/operation/commands/explain/result.rb +52 -0
  65. data/lib/mongo/operation/commands/indexes.rb +1 -1
  66. data/lib/mongo/operation/commands/list_collections.rb +1 -1
  67. data/lib/mongo/operation/commands/list_collections/result.rb +1 -1
  68. data/lib/mongo/operation/commands/list_indexes.rb +1 -1
  69. data/lib/mongo/operation/commands/list_indexes/result.rb +1 -1
  70. data/lib/mongo/operation/commands/map_reduce.rb +8 -4
  71. data/lib/mongo/operation/commands/map_reduce/result.rb +13 -1
  72. data/lib/mongo/operation/commands/user_query.rb +1 -1
  73. data/lib/mongo/operation/commands/users_info.rb +6 -2
  74. data/lib/mongo/operation/executable.rb +4 -1
  75. data/lib/mongo/operation/read_preference.rb +10 -5
  76. data/lib/mongo/operation/result.rb +26 -2
  77. data/lib/mongo/operation/specifiable.rb +13 -1
  78. data/lib/mongo/operation/uses_command_op_msg.rb +47 -0
  79. data/lib/mongo/operation/write/bulk/bulkable.rb +4 -1
  80. data/lib/mongo/operation/write/bulk/insert/result.rb +4 -4
  81. data/lib/mongo/operation/write/command/create_index.rb +6 -1
  82. data/lib/mongo/operation/write/command/delete.rb +28 -4
  83. data/lib/mongo/operation/write/command/drop_index.rb +6 -1
  84. data/lib/mongo/operation/write/command/insert.rb +22 -18
  85. data/lib/mongo/operation/write/command/update.rb +24 -9
  86. data/lib/mongo/operation/write/command/writable.rb +14 -1
  87. data/lib/mongo/operation/write/insert.rb +4 -1
  88. data/lib/mongo/operation/write/insert/result.rb +2 -2
  89. data/lib/mongo/operation/write/update.rb +7 -1
  90. data/lib/mongo/operation/write/write_command_enabled.rb +20 -3
  91. data/lib/mongo/protocol.rb +3 -0
  92. data/lib/mongo/protocol/bit_vector.rb +2 -2
  93. data/lib/mongo/protocol/compressed.rb +135 -0
  94. data/lib/mongo/protocol/delete.rb +8 -6
  95. data/lib/mongo/protocol/get_more.rb +8 -6
  96. data/lib/mongo/protocol/insert.rb +8 -6
  97. data/lib/mongo/protocol/kill_cursors.rb +8 -6
  98. data/lib/mongo/protocol/message.rb +31 -3
  99. data/lib/mongo/protocol/msg.rb +172 -0
  100. data/lib/mongo/protocol/query.rb +26 -6
  101. data/lib/mongo/protocol/registry.rb +76 -0
  102. data/lib/mongo/protocol/reply.rb +10 -5
  103. data/lib/mongo/protocol/serializers.rb +224 -0
  104. data/lib/mongo/protocol/update.rb +8 -6
  105. data/lib/mongo/retryable.rb +4 -2
  106. data/lib/mongo/server.rb +6 -3
  107. data/lib/mongo/server/connectable.rb +1 -1
  108. data/lib/mongo/server/connection.rb +30 -8
  109. data/lib/mongo/server/description.rb +25 -1
  110. data/lib/mongo/server/description/features.rb +4 -1
  111. data/lib/mongo/server/monitor.rb +5 -0
  112. data/lib/mongo/server/monitor/connection.rb +50 -2
  113. data/lib/mongo/server_selector/nearest.rb +10 -4
  114. data/lib/mongo/server_selector/primary.rb +20 -0
  115. data/lib/mongo/server_selector/primary_preferred.rb +10 -4
  116. data/lib/mongo/server_selector/secondary.rb +10 -4
  117. data/lib/mongo/server_selector/secondary_preferred.rb +24 -4
  118. data/lib/mongo/session.rb +180 -0
  119. data/lib/mongo/session/server_session.rb +73 -0
  120. data/lib/mongo/session/session_pool.rb +161 -0
  121. data/lib/mongo/uri.rb +11 -0
  122. data/lib/mongo/version.rb +1 -1
  123. data/mongo.gemspec +2 -1
  124. data/spec/mongo/auth/cr_spec.rb +12 -0
  125. data/spec/mongo/auth/ldap_spec.rb +2 -0
  126. data/spec/mongo/auth/scram/conversation_spec.rb +6 -6
  127. data/spec/mongo/auth/scram_spec.rb +25 -1
  128. data/spec/mongo/auth/user/view_spec.rb +268 -76
  129. data/spec/mongo/auth/x509_spec.rb +2 -0
  130. data/spec/mongo/bulk_write_spec.rb +435 -5
  131. data/spec/mongo/client_spec.rb +356 -39
  132. data/spec/mongo/cluster/app_metadata_spec.rb +2 -2
  133. data/spec/mongo/cluster_spec.rb +176 -0
  134. data/spec/mongo/collection/view/aggregation_spec.rb +33 -12
  135. data/spec/mongo/collection/view/builder/find_command_spec.rb +46 -6
  136. data/spec/mongo/collection/view/change_stream_spec.rb +814 -0
  137. data/spec/mongo/collection/view/map_reduce_spec.rb +94 -17
  138. data/spec/mongo/collection/view/readable_spec.rb +3 -12
  139. data/spec/mongo/collection_spec.rb +1048 -42
  140. data/spec/mongo/cursor/builder/get_more_command_spec.rb +19 -0
  141. data/spec/mongo/cursor_spec.rb +2 -2
  142. data/spec/mongo/database_spec.rb +50 -1
  143. data/spec/mongo/grid/fs_bucket_spec.rb +225 -137
  144. data/spec/mongo/grid/stream/read_spec.rb +2 -2
  145. data/spec/mongo/index/view_spec.rb +146 -8
  146. data/spec/mongo/monitoring/event/secure_spec.rb +42 -0
  147. data/spec/mongo/operation/read/query_spec.rb +2 -1
  148. data/spec/mongo/operation/specifiable_spec.rb +2 -2
  149. data/spec/mongo/operation/write/command/delete_spec.rb +96 -13
  150. data/spec/mongo/operation/write/command/insert_spec.rb +111 -12
  151. data/spec/mongo/operation/write/command/update_spec.rb +93 -10
  152. data/spec/mongo/operation/write/delete_spec.rb +1 -1
  153. data/spec/mongo/operation/write/insert_spec.rb +1 -1
  154. data/spec/mongo/operation/write/update_spec.rb +1 -1
  155. data/spec/mongo/protocol/compressed_spec.rb +66 -0
  156. data/spec/mongo/protocol/delete_spec.rb +14 -0
  157. data/spec/mongo/protocol/get_more_spec.rb +14 -0
  158. data/spec/mongo/protocol/insert_spec.rb +14 -0
  159. data/spec/mongo/protocol/kill_cursors_spec.rb +14 -0
  160. data/spec/mongo/protocol/msg_spec.rb +499 -0
  161. data/spec/mongo/protocol/query_spec.rb +45 -0
  162. data/spec/mongo/protocol/registry_spec.rb +31 -0
  163. data/spec/mongo/protocol/reply_spec.rb +14 -0
  164. data/spec/mongo/protocol/update_spec.rb +14 -0
  165. data/spec/mongo/retryable_spec.rb +6 -2
  166. data/spec/mongo/sdam_spec.rb +4 -0
  167. data/spec/mongo/server/connection_spec.rb +4 -2
  168. data/spec/mongo/server/description_spec.rb +28 -1
  169. data/spec/mongo/session/server_session_spec.rb +16 -0
  170. data/spec/mongo/session/session_pool_spec.rb +194 -0
  171. data/spec/mongo/uri_spec.rb +31 -2
  172. data/spec/spec_helper.rb +104 -0
  173. data/spec/support/authorization.rb +6 -1
  174. data/spec/support/crud.rb +3 -1
  175. data/spec/support/crud/write.rb +6 -1
  176. data/spec/support/crud_tests/write/findOneAndUpdate-arrayFilters.yml +69 -0
  177. data/spec/support/crud_tests/write/updateMany-arrayFilters.yml +63 -0
  178. data/spec/support/crud_tests/write/updateOne-arrayFilters.yml +109 -0
  179. data/spec/support/sdam/rs/discover_arbiters.yml +1 -1
  180. data/spec/support/sdam/rs/discover_passives.yml +2 -2
  181. data/spec/support/sdam/rs/discover_primary.yml +1 -1
  182. data/spec/support/sdam/rs/discover_secondary.yml +1 -1
  183. data/spec/support/sdam/rs/discovery.yml +4 -4
  184. data/spec/support/sdam/rs/equal_electionids.yml +1 -0
  185. data/spec/support/sdam/rs/ghost_discovered.yml +1 -1
  186. data/spec/support/sdam/rs/hosts_differ_from_seeds.yml +1 -1
  187. data/spec/support/sdam/rs/ls_timeout.yml +88 -0
  188. data/spec/support/sdam/rs/member_reconfig.yml +2 -2
  189. data/spec/support/sdam/rs/member_standalone.yml +2 -2
  190. data/spec/support/sdam/rs/new_primary.yml +2 -2
  191. data/spec/support/sdam/rs/new_primary_new_electionid.yml +3 -0
  192. data/spec/support/sdam/rs/new_primary_new_setversion.yml +3 -0
  193. data/spec/support/sdam/rs/new_primary_wrong_set_name.yml +2 -2
  194. data/spec/support/sdam/rs/non_rs_member.yml +1 -1
  195. data/spec/support/sdam/rs/normalize_case.yml +1 -1
  196. data/spec/support/sdam/rs/null_election_id.yml +4 -0
  197. data/spec/support/sdam/rs/primary_becomes_standalone.yml +2 -2
  198. data/spec/support/sdam/rs/primary_changes_set_name.yml +2 -2
  199. data/spec/support/sdam/rs/primary_disconnect.yml +2 -2
  200. data/spec/support/sdam/rs/primary_disconnect_electionid.yml +5 -0
  201. data/spec/support/sdam/rs/primary_disconnect_setversion.yml +5 -0
  202. data/spec/support/sdam/rs/primary_hint_from_secondary_with_mismatched_me.yml +58 -0
  203. data/spec/support/sdam/rs/primary_reports_new_member.yml +4 -4
  204. data/spec/support/sdam/rs/primary_to_no_primary_mismatched_me.yml +2 -2
  205. data/spec/support/sdam/rs/primary_wrong_set_name.yml +1 -1
  206. data/spec/support/sdam/rs/response_from_removed.yml +2 -2
  207. data/spec/support/sdam/rs/rsother_discovered.yml +1 -1
  208. data/spec/support/sdam/rs/sec_not_auth.yml +1 -1
  209. data/spec/support/sdam/rs/secondary_wrong_set_name.yml +1 -1
  210. data/spec/support/sdam/rs/secondary_wrong_set_name_with_primary.yml +2 -2
  211. data/spec/support/sdam/rs/setversion_without_electionid.yml +2 -0
  212. data/spec/support/sdam/rs/stepdown_change_set_name.yml +2 -2
  213. data/spec/support/sdam/rs/unexpected_mongos.yml +1 -1
  214. data/spec/support/sdam/rs/use_setversion_without_electionid.yml +3 -0
  215. data/spec/support/sdam/rs/wrong_set_name.yml +1 -1
  216. data/spec/support/sdam/sharded/ls_timeout_mongos.yml +97 -0
  217. data/spec/support/sdam/sharded/mongos_disconnect.yml +3 -3
  218. data/spec/support/sdam/sharded/multiple_mongoses.yml +1 -1
  219. data/spec/support/sdam/sharded/non_mongos_removed.yml +1 -1
  220. data/spec/support/sdam/sharded/normalize_uri_case.yml +1 -1
  221. data/spec/support/sdam/single/direct_connection_external_ip.yml +1 -1
  222. data/spec/support/sdam/single/direct_connection_mongos.yml +1 -1
  223. data/spec/support/sdam/single/direct_connection_rsarbiter.yml +1 -1
  224. data/spec/support/sdam/single/direct_connection_rsprimary.yml +1 -1
  225. data/spec/support/sdam/single/direct_connection_rssecondary.yml +1 -1
  226. data/spec/support/sdam/single/direct_connection_slave.yml +1 -1
  227. data/spec/support/sdam/single/direct_connection_standalone.yml +1 -1
  228. data/spec/support/sdam/single/ls_timeout_standalone.yml +35 -0
  229. data/spec/support/sdam/single/not_ok_response.yml +1 -1
  230. data/spec/support/sdam/single/standalone_removed.yml +1 -1
  231. data/spec/support/sdam/single/unavailable_seed.yml +1 -1
  232. data/spec/support/server_discovery_and_monitoring.rb +4 -0
  233. data/spec/support/shared/session.rb +236 -0
  234. metadata +53 -15
  235. metadata.gz.sig +0 -0
@@ -17,6 +17,7 @@ require 'mongo/collection/view/immutable'
17
17
  require 'mongo/collection/view/iterable'
18
18
  require 'mongo/collection/view/explainable'
19
19
  require 'mongo/collection/view/aggregation'
20
+ require 'mongo/collection/view/change_stream'
20
21
  require 'mongo/collection/view/map_reduce'
21
22
  require 'mongo/collection/view/readable'
22
23
  require 'mongo/collection/view/writable'
@@ -59,8 +60,7 @@ module Mongo
59
60
  def_delegators :collection,
60
61
  :client,
61
62
  :cluster,
62
- :database,
63
- :read_preference
63
+ :database
64
64
 
65
65
  # Delegate to the cluster for the next primary.
66
66
  def_delegators :cluster, :next_primary
@@ -198,6 +198,12 @@ module Mongo
198
198
  end
199
199
 
200
200
  def view; self; end
201
+
202
+ def with_session
203
+ client.send(:with_session, @options) do |session|
204
+ yield(session)
205
+ end
206
+ end
201
207
  end
202
208
  end
203
209
  end
@@ -37,7 +37,7 @@ module Mongo
37
37
  def_delegators :view, :collection, :read, :cluster
38
38
 
39
39
  # Delegate necessary operations to the collection.
40
- def_delegators :collection, :database
40
+ def_delegators :collection, :database, :client
41
41
 
42
42
  # The reroute message.
43
43
  #
@@ -90,16 +90,20 @@ module Mongo
90
90
 
91
91
  private
92
92
 
93
- def aggregate_spec
94
- Builder::Aggregation.new(pipeline, view, options).specification
93
+ def server_selector
94
+ @view.send(:server_selector)
95
+ end
96
+
97
+ def aggregate_spec(session)
98
+ Builder::Aggregation.new(pipeline, view, options.merge(session: session)).specification
95
99
  end
96
100
 
97
101
  def new(options)
98
102
  Aggregation.new(view, pipeline, options)
99
103
  end
100
104
 
101
- def initial_query_op
102
- Operation::Commands::Aggregate.new(aggregate_spec)
105
+ def initial_query_op(session)
106
+ Operation::Commands::Aggregate.new(aggregate_spec(session))
103
107
  end
104
108
 
105
109
  def valid_server?(server)
@@ -110,13 +114,13 @@ module Mongo
110
114
  pipeline.none? { |op| op.key?('$out') || op.key?(:$out) }
111
115
  end
112
116
 
113
- def send_initial_query(server)
117
+ def send_initial_query(server, session)
114
118
  unless valid_server?(server)
115
119
  log_warn(REROUTE)
116
120
  server = cluster.next_primary(false)
117
121
  end
118
122
  validate_collation!(server)
119
- initial_query_op.execute(server)
123
+ initial_query_op(session).execute(server)
120
124
  end
121
125
 
122
126
  def validate_collation!(server)
@@ -29,6 +29,9 @@ module Mongo
29
29
  MAPPINGS = BSON::Document.new(
30
30
  :allow_disk_use => 'allowDiskUse',
31
31
  :max_time_ms => 'maxTimeMS',
32
+ # This is intentional; max_await_time_ms is an alias for maxTimeMS used on getmore
33
+ # commands for change streams.
34
+ :max_await_time_ms => 'maxTimeMS',
32
35
  :explain => 'explain',
33
36
  :bypass_document_validation => 'bypassDocumentValidation',
34
37
  :collation => 'collation',
@@ -74,7 +77,8 @@ module Mongo
74
77
  spec = {
75
78
  selector: aggregation_command,
76
79
  db_name: database.name,
77
- read: read
80
+ read: read,
81
+ session: @options[:session]
78
82
  }
79
83
  write? ? spec.merge!(write_concern: write_concern) : spec
80
84
  end
@@ -48,7 +48,6 @@ module Mongo
48
48
  no_cursor_timeout: 'noCursorTimeout',
49
49
  await_data: 'awaitData',
50
50
  allow_partial_results: 'allowPartialResults',
51
- read_concern: 'readConcern',
52
51
  collation: 'collation'
53
52
  ).freeze
54
53
 
@@ -73,10 +72,12 @@ module Mongo
73
72
  # FindCommandBuilder.new(view)
74
73
  #
75
74
  # @param [ Collection::View ] view The collection view.
75
+ # @param [ Session ] session The session.
76
76
  #
77
77
  # @since 2.2.2
78
- def initialize(view)
78
+ def initialize(view, session)
79
79
  @view = view
80
+ @session = session
80
81
  end
81
82
 
82
83
  # Get the specification to pass to the find command operation.
@@ -88,13 +89,14 @@ module Mongo
88
89
  #
89
90
  # @since 2.2.0
90
91
  def specification
91
- { selector: find_command, db_name: database.name, read: read }
92
+ { selector: find_command, db_name: database.name, read: read, session: @session }
92
93
  end
93
94
 
94
95
  private
95
96
 
96
97
  def find_command
97
98
  document = BSON::Document.new('find' => collection.name, 'filter' => filter)
99
+ document[:readConcern] = collection.read_concern if collection.read_concern
98
100
  command = Options::Mapper.transform_documents(convert_flags(options), MAPPINGS, document)
99
101
  convert_limit_and_batch_size(command)
100
102
  command
@@ -81,7 +81,8 @@ module Mongo
81
81
  {
82
82
  selector: find_command,
83
83
  db_name: query_database,
84
- read: read
84
+ read: read,
85
+ session: options[:session]
85
86
  }
86
87
  end
87
88
 
@@ -109,7 +110,8 @@ module Mongo
109
110
  spec = {
110
111
  selector: map_reduce_command,
111
112
  db_name: database.name,
112
- read: read
113
+ read: read,
114
+ session: options[:session]
113
115
  }
114
116
  write?(spec) ? spec.merge!(write_concern: write_concern) : spec
115
117
  end
@@ -138,7 +140,7 @@ module Mongo
138
140
  :out => { inline: 1 }
139
141
  )
140
142
  command[:readConcern] = collection.read_concern if collection.read_concern
141
- command.merge!(view.options)
143
+ command.merge!(view_options)
142
144
  command.merge!(Options::Mapper.transform_documents(options, MAPPINGS))
143
145
  command
144
146
  end
@@ -152,6 +154,12 @@ module Mongo
152
154
  options[:out][OUT_ACTIONS.find { |action| options[:out][action] }]
153
155
  end || options[:out]
154
156
  end
157
+
158
+ def view_options
159
+ @view_options ||= (opts = view.options.dup
160
+ opts.delete(:session)
161
+ opts)
162
+ end
155
163
  end
156
164
  end
157
165
  end
@@ -68,7 +68,7 @@ module Mongo
68
68
  end
69
69
 
70
70
  def read_pref_formatted
71
- @read_formatted ||= read.to_mongos
71
+ @read_formatted ||= ServerSelector.get(read).to_mongos if read
72
72
  end
73
73
 
74
74
  def special_filter
@@ -0,0 +1,160 @@
1
+ # Copyright (C) 2017 MongoDB, Inc.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ require 'mongo/collection/view/change_stream/retryable'
16
+
17
+ module Mongo
18
+ class Collection
19
+ class View
20
+
21
+ # Provides behaviour around a `$changeStream` pipeline stage in the
22
+ # aggregation framework. Specifying this stage allows users to request that
23
+ # notifications are sent for all changes to a particular collection or database.
24
+ #
25
+ # @note Only available in server versions 3.6 and higher.
26
+ # @note ChangeStreams do not work properly with JRuby because of the issue documented
27
+ # here: https://github.com/jruby/jruby/issues/4212
28
+ # Namely, JRuby eagerly evaluates #next on an Enumerator in a background green thread.
29
+ # So calling #next on the change stream will cause getmores to be called in a loop in the background.
30
+ #
31
+ #
32
+ # @since 2.5.0
33
+ class ChangeStream < Aggregation
34
+ include Retryable
35
+
36
+ # @return [ String ] The fullDocument option default value.
37
+ #
38
+ # @since 2.5.0
39
+ FULL_DOCUMENT_DEFAULT = 'default'.freeze
40
+
41
+ # @return [ BSON::Document ] The change stream options.
42
+ #
43
+ # @since 2.5.0
44
+ attr_reader :options
45
+
46
+ # Initialize the change stream for the provided collection view, pipeline
47
+ # and options.
48
+ #
49
+ # @example Create the new change stream view.
50
+ # ChangeStream.new(view, pipeline, options)
51
+ #
52
+ # @param [ Collection::View ] view The collection view.
53
+ # @param [ Array<Hash> ] pipeline The pipeline of operators to filter the change notifications.
54
+ # @param [ Hash ] opts The change stream options.
55
+ #
56
+ # @option options [ String ] :full_document Allowed values: ‘default’, ‘updateLookup’. Defaults to ‘default’.
57
+ # When set to ‘updateLookup’, the change notification for partial updates will include both a delta
58
+ # describing the changes to the document, as well as a copy of the entire document that was changed
59
+ # from some time after the change occurred.
60
+ # @option options [ BSON::Document, Hash ] :resume_after Specifies the logical starting point for the
61
+ # new change stream.
62
+ # @option options [ Integer ] :max_await_time_ms The maximum amount of time for the server to wait
63
+ # on new documents to satisfy a change stream query.
64
+ # @option options [ Integer ] :batch_size The number of documents to return per batch.
65
+ # @option options [ BSON::Document, Hash ] :collation The collation to use.
66
+ #
67
+ # @since 2.5.0
68
+ def initialize(view, pipeline, options = {})
69
+ @view = view
70
+ @change_stream_filters = pipeline && pipeline.dup
71
+ @options = options && options.dup.freeze
72
+ @resume_token = @options[:resume_after]
73
+ read_with_one_retry { create_cursor! }
74
+ end
75
+
76
+ # Iterate through documents returned by the change stream.
77
+ #
78
+ # @example Iterate through the stream of documents.
79
+ # stream.each do |document|
80
+ # p document
81
+ # end
82
+ #
83
+ # @return [ Enumerator ] The enumerator.
84
+ #
85
+ # @since 2.5.0
86
+ #
87
+ # @yieldparam [ BSON::Document ] Each change stream document.
88
+ def each
89
+ raise StopIteration.new if closed?
90
+ begin
91
+ @cursor.each do |doc|
92
+ cache_resume_token(doc)
93
+ yield doc
94
+ end if block_given?
95
+ @cursor.to_enum
96
+ rescue => e
97
+ close
98
+ if retryable?(e)
99
+ create_cursor!
100
+ retry
101
+ end
102
+ raise
103
+ end
104
+ end
105
+
106
+ # Close the change stream.
107
+ #
108
+ # @example Close the change stream.
109
+ # stream.close
110
+ #
111
+ # @return [ nil ] nil.
112
+ #
113
+ # @since 2.5.0
114
+ def close
115
+ unless closed?
116
+ begin; @cursor.send(:kill_cursors); rescue; end
117
+ @cursor = nil
118
+ end
119
+ end
120
+
121
+ # Is the change stream closed?
122
+ #
123
+ # @example Determine whether the change stream is closed.
124
+ # stream.closed?
125
+ #
126
+ # @return [ true, false ] If the change stream is closed.
127
+ #
128
+ # @since 2.5.0
129
+ def closed?
130
+ @cursor.nil?
131
+ end
132
+
133
+ private
134
+
135
+ def cache_resume_token(doc)
136
+ unless @resume_token = (doc[:_id] && doc[:_id].dup)
137
+ raise Error::MissingResumeToken.new
138
+ end
139
+ end
140
+
141
+ def create_cursor!
142
+ session = client.send(:get_session, @options)
143
+ server = server_selector.select_server(cluster, false)
144
+ result = send_initial_query(server, session)
145
+ @cursor = Cursor.new(view, result, server, disable_retry: true, session: session)
146
+ end
147
+
148
+ def pipeline
149
+ change_doc = { fullDocument: ( @options[:full_document] || FULL_DOCUMENT_DEFAULT ) }
150
+ change_doc[:resumeAfter] = @resume_token if @resume_token
151
+ [{ '$changeStream' => change_doc }] + @change_stream_filters
152
+ end
153
+
154
+ def send_initial_query(server, session)
155
+ initial_query_op(session).execute(server)
156
+ end
157
+ end
158
+ end
159
+ end
160
+ end
@@ -0,0 +1,57 @@
1
+ # Copyright (C) 2017 MongoDB, Inc.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ module Mongo
16
+ class Collection
17
+ class View
18
+ class ChangeStream < Aggregation
19
+
20
+ # Behavior around resuming a change stream.
21
+ #
22
+ # @since 2.5.0
23
+ module Retryable
24
+
25
+ private
26
+
27
+ RETRY_MESSAGES = [
28
+ 'not master',
29
+ '(43)' # cursor not found error code
30
+ ].freeze
31
+
32
+ def read_with_one_retry
33
+ yield
34
+ rescue => e
35
+ if retryable?(e)
36
+ yield
37
+ else
38
+ raise(e)
39
+ end
40
+ end
41
+
42
+ def retryable?(error)
43
+ network_error?(error) || retryable_operation_failure?(error)
44
+ end
45
+
46
+ def network_error?(error)
47
+ [ Error::SocketError, Error::SocketTimeoutError].include?(error.class)
48
+ end
49
+
50
+ def retryable_operation_failure?(error)
51
+ error.is_a?(Error::OperationFailure) && RETRY_MESSAGES.any? { |m| error.message.include?(m) }
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
@@ -36,10 +36,11 @@ module Mongo
36
36
  # @yieldparam [ Hash ] Each matching document.
37
37
  def each
38
38
  @cursor = nil
39
+ session = client.send(:get_session, @options)
39
40
  read_with_retry do
40
- server = read.select_server(cluster, false)
41
- result = send_initial_query(server)
42
- @cursor = Cursor.new(view, result, server)
41
+ server = server_selector.select_server(cluster, false)
42
+ result = send_initial_query(server, session)
43
+ @cursor = Cursor.new(view, result, server, session: session)
43
44
  end
44
45
  @cursor.each do |doc|
45
46
  yield doc
@@ -60,25 +61,25 @@ module Mongo
60
61
 
61
62
  private
62
63
 
63
- def initial_query_op(server)
64
+ def initial_query_op(server, session)
64
65
  if server.features.find_command_enabled?
65
- initial_command_op
66
+ initial_command_op(session)
66
67
  else
67
68
  Operation::Read::Query.new(Builder::OpQuery.new(self).specification)
68
69
  end
69
70
  end
70
71
 
71
- def initial_command_op
72
+ def initial_command_op(session)
72
73
  if explained?
73
- Operation::Commands::Command.new(Builder::FindCommand.new(self).explain_specification)
74
+ Operation::Commands::Explain.new(Builder::FindCommand.new(self, session).explain_specification)
74
75
  else
75
- Operation::Commands::Find.new(Builder::FindCommand.new(self).specification)
76
+ Operation::Commands::Find.new(Builder::FindCommand.new(self, session).specification)
76
77
  end
77
78
  end
78
79
 
79
- def send_initial_query(server)
80
+ def send_initial_query(server, session = nil)
80
81
  validate_collation!(server, collation)
81
- initial_query_op(server).execute(server)
82
+ initial_query_op(server, session).execute(server)
82
83
  end
83
84
  end
84
85
  end