mongo 2.3.1 → 2.4.0.rc0

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 (170) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data.tar.gz.sig +2 -3
  4. data/lib/mongo/bulk_write.rb +8 -7
  5. data/lib/mongo/bulk_write/combineable.rb +4 -0
  6. data/lib/mongo/bulk_write/transformable.rb +17 -5
  7. data/lib/mongo/bulk_write/validatable.rb +1 -0
  8. data/lib/mongo/client.rb +3 -0
  9. data/lib/mongo/cluster.rb +8 -0
  10. data/lib/mongo/cluster/app_metadata.rb +135 -0
  11. data/lib/mongo/collection.rb +42 -10
  12. data/lib/mongo/collection/view.rb +15 -1
  13. data/lib/mongo/collection/view/aggregation.rb +5 -0
  14. data/lib/mongo/collection/view/builder/aggregation.rb +13 -3
  15. data/lib/mongo/collection/view/builder/find_command.rb +7 -21
  16. data/lib/mongo/collection/view/builder/map_reduce.rb +22 -5
  17. data/lib/mongo/collection/view/iterable.rb +1 -0
  18. data/lib/mongo/collection/view/map_reduce.rb +5 -0
  19. data/lib/mongo/collection/view/readable.rb +35 -14
  20. data/lib/mongo/collection/view/writable.rb +54 -23
  21. data/lib/mongo/cursor/builder/get_more_command.rb +2 -3
  22. data/lib/mongo/database.rb +10 -2
  23. data/lib/mongo/error.rb +2 -0
  24. data/lib/mongo/error/invalid_application_name.rb +38 -0
  25. data/lib/mongo/error/invalid_server_preference.rb +24 -3
  26. data/lib/mongo/error/unsupported_collation.rb +51 -0
  27. data/lib/mongo/index/view.rb +28 -15
  28. data/lib/mongo/operation.rb +6 -0
  29. data/lib/mongo/operation/commands.rb +3 -0
  30. data/lib/mongo/operation/commands/aggregate.rb +10 -10
  31. data/lib/mongo/operation/commands/create.rb +45 -0
  32. data/lib/mongo/operation/commands/drop.rb +45 -0
  33. data/lib/mongo/operation/commands/drop_database.rb +45 -0
  34. data/lib/mongo/operation/commands/map_reduce.rb +12 -1
  35. data/lib/mongo/operation/commands/parallel_scan.rb +1 -0
  36. data/lib/mongo/operation/read_preference.rb +9 -9
  37. data/lib/mongo/operation/specifiable.rb +34 -0
  38. data/lib/mongo/operation/takes_write_concern.rb +35 -0
  39. data/lib/mongo/operation/write/bulk/bulkable.rb +1 -1
  40. data/lib/mongo/operation/write/command/create_index.rb +6 -0
  41. data/lib/mongo/operation/write/command/drop_index.rb +6 -0
  42. data/lib/mongo/operation/write/command/insert.rb +1 -1
  43. data/lib/mongo/operation/write/command/update.rb +1 -0
  44. data/lib/mongo/operation/write/command/writable.rb +2 -2
  45. data/lib/mongo/operation/write/create_index.rb +2 -2
  46. data/lib/mongo/operation/write/create_user.rb +1 -1
  47. data/lib/mongo/operation/write/delete.rb +5 -1
  48. data/lib/mongo/operation/write/gle.rb +1 -1
  49. data/lib/mongo/operation/write/insert.rb +2 -2
  50. data/lib/mongo/operation/write/remove_user.rb +1 -1
  51. data/lib/mongo/operation/write/update.rb +5 -1
  52. data/lib/mongo/operation/write/update_user.rb +1 -1
  53. data/lib/mongo/operation/write/write_command_enabled.rb +10 -2
  54. data/lib/mongo/protocol/insert.rb +1 -2
  55. data/lib/mongo/protocol/query.rb +3 -7
  56. data/lib/mongo/server.rb +8 -3
  57. data/lib/mongo/server/connection.rb +17 -11
  58. data/lib/mongo/server/description.rb +22 -0
  59. data/lib/mongo/server/description/features.rb +2 -0
  60. data/lib/mongo/server/monitor.rb +5 -0
  61. data/lib/mongo/server/monitor/connection.rb +11 -0
  62. data/lib/mongo/server_selector/nearest.rb +9 -6
  63. data/lib/mongo/server_selector/primary.rb +4 -0
  64. data/lib/mongo/server_selector/primary_preferred.rb +7 -1
  65. data/lib/mongo/server_selector/secondary.rb +5 -0
  66. data/lib/mongo/server_selector/secondary_preferred.rb +7 -2
  67. data/lib/mongo/server_selector/selectable.rb +57 -10
  68. data/lib/mongo/socket/ssl.rb +1 -0
  69. data/lib/mongo/uri.rb +4 -0
  70. data/lib/mongo/version.rb +1 -1
  71. data/lib/mongo/write_concern.rb +1 -0
  72. data/mongo.gemspec +1 -1
  73. data/spec/mongo/auth/cr_spec.rb +7 -1
  74. data/spec/mongo/auth/ldap_spec.rb +7 -1
  75. data/spec/mongo/auth/scram_spec.rb +7 -1
  76. data/spec/mongo/auth/x509_spec.rb +7 -1
  77. data/spec/mongo/bulk_write_spec.rb +598 -5
  78. data/spec/mongo/client_spec.rb +47 -1
  79. data/spec/mongo/cluster/app_metadata_spec.rb +104 -0
  80. data/spec/mongo/cluster/topology/replica_set_spec.rb +14 -8
  81. data/spec/mongo/cluster/topology/sharded_spec.rb +9 -3
  82. data/spec/mongo/cluster/topology/single_spec.rb +10 -4
  83. data/spec/mongo/cluster_spec.rb +29 -0
  84. data/spec/mongo/collection/view/aggregation_spec.rb +139 -0
  85. data/spec/mongo/collection/view/builder/find_command_spec.rb +6 -243
  86. data/spec/mongo/collection/view/map_reduce_spec.rb +104 -0
  87. data/spec/mongo/collection/view/readable_spec.rb +83 -0
  88. data/spec/mongo/collection/view/writable_spec.rb +447 -1
  89. data/spec/mongo/collection/view_spec.rb +57 -0
  90. data/spec/mongo/collection_spec.rb +926 -101
  91. data/spec/mongo/crud_spec.rb +4 -5
  92. data/spec/mongo/database_spec.rb +99 -1
  93. data/spec/mongo/index/view_spec.rb +360 -31
  94. data/spec/mongo/max_staleness_spec.rb +108 -0
  95. data/spec/mongo/operation/read_preference_spec.rb +8 -8
  96. data/spec/mongo/operation/write/command/delete_spec.rb +1 -1
  97. data/spec/mongo/operation/write/command/insert_spec.rb +1 -1
  98. data/spec/mongo/operation/write/command/update_spec.rb +1 -1
  99. data/spec/mongo/server/connection_pool_spec.rb +3 -1
  100. data/spec/mongo/server/connection_spec.rb +17 -7
  101. data/spec/mongo/server/description/features_spec.rb +50 -0
  102. data/spec/mongo/server/description_spec.rb +9 -3
  103. data/spec/mongo/server_selection_spec.rb +5 -3
  104. data/spec/mongo/server_selector/nearest_spec.rb +73 -0
  105. data/spec/mongo/server_selector/primary_preferred_spec.rb +73 -0
  106. data/spec/mongo/server_selector/primary_spec.rb +36 -0
  107. data/spec/mongo/server_selector/secondary_preferred_spec.rb +73 -0
  108. data/spec/mongo/server_selector/secondary_spec.rb +73 -0
  109. data/spec/mongo/server_selector_spec.rb +53 -0
  110. data/spec/mongo/server_spec.rb +3 -1
  111. data/spec/mongo/uri_spec.rb +54 -0
  112. data/spec/mongo/write_concern_spec.rb +18 -0
  113. data/spec/spec_helper.rb +10 -0
  114. data/spec/support/authorization.rb +8 -1
  115. data/spec/support/crud.rb +15 -0
  116. data/spec/support/crud/read.rb +27 -19
  117. data/spec/support/crud/write.rb +28 -3
  118. data/spec/support/crud_tests/read/aggregate.yml +15 -3
  119. data/spec/support/crud_tests/read/count.yml +14 -3
  120. data/spec/support/crud_tests/read/distinct.yml +13 -1
  121. data/spec/support/crud_tests/read/find.yml +12 -2
  122. data/spec/support/crud_tests/write/deleteMany.yml +22 -1
  123. data/spec/support/crud_tests/write/deleteOne.yml +20 -1
  124. data/spec/support/crud_tests/write/findOneAndDelete.yml +27 -2
  125. data/spec/support/crud_tests/write/findOneAndReplace.yml +43 -14
  126. data/spec/support/crud_tests/write/findOneAndUpdate.yml +50 -8
  127. data/spec/support/crud_tests/write/replaceOne.yml +34 -10
  128. data/spec/support/crud_tests/write/updateMany.yml +42 -11
  129. data/spec/support/crud_tests/write/updateOne.yml +32 -7
  130. data/spec/support/max_staleness/ReplicaSetNoPrimary/DefaultNoMaxStaleness.yml +26 -0
  131. data/spec/support/max_staleness/ReplicaSetNoPrimary/Incompatible.yml +25 -0
  132. data/spec/support/max_staleness/ReplicaSetNoPrimary/LastUpdateTime.yml +33 -0
  133. data/spec/support/max_staleness/ReplicaSetNoPrimary/Nearest.yml +33 -0
  134. data/spec/support/max_staleness/ReplicaSetNoPrimary/Nearest2.yml +33 -0
  135. data/spec/support/max_staleness/ReplicaSetNoPrimary/PrimaryPreferred.yml +27 -0
  136. data/spec/support/max_staleness/ReplicaSetNoPrimary/PrimaryPreferred_tags.yml +36 -0
  137. data/spec/support/max_staleness/ReplicaSetNoPrimary/Secondary.yml +51 -0
  138. data/spec/support/max_staleness/ReplicaSetNoPrimary/SecondaryPreferred.yml +26 -0
  139. data/spec/support/max_staleness/ReplicaSetNoPrimary/SecondaryPreferred_tags.yml +51 -0
  140. data/spec/support/max_staleness/ReplicaSetWithPrimary/DefaultNoMaxStaleness.yml +26 -0
  141. data/spec/support/max_staleness/ReplicaSetWithPrimary/Incompatible.yml +25 -0
  142. data/spec/support/max_staleness/ReplicaSetWithPrimary/LastUpdateTime.yml +35 -0
  143. data/spec/support/max_staleness/ReplicaSetWithPrimary/MaxStalenessTooSmall.yml +25 -0
  144. data/spec/support/max_staleness/ReplicaSetWithPrimary/MaxStalenessWithModePrimary.yml +23 -0
  145. data/spec/support/max_staleness/ReplicaSetWithPrimary/Nearest.yml +33 -0
  146. data/spec/support/max_staleness/ReplicaSetWithPrimary/Nearest2.yml +33 -0
  147. data/spec/support/max_staleness/ReplicaSetWithPrimary/Nearest_tags.yml +36 -0
  148. data/spec/support/max_staleness/ReplicaSetWithPrimary/PrimaryPreferred.yml +27 -0
  149. data/spec/support/max_staleness/ReplicaSetWithPrimary/PrimaryPreferred_incompatible.yml +27 -0
  150. data/spec/support/max_staleness/ReplicaSetWithPrimary/SecondaryPreferred.yml +26 -0
  151. data/spec/support/max_staleness/ReplicaSetWithPrimary/SecondaryPreferred_tags.yml +59 -0
  152. data/spec/support/max_staleness/ReplicaSetWithPrimary/SecondaryPreferred_tags2.yml +43 -0
  153. data/spec/support/max_staleness/ReplicaSetWithPrimary/Secondary_tags.yml +59 -0
  154. data/spec/support/max_staleness/ReplicaSetWithPrimary/Secondary_tags2.yml +43 -0
  155. data/spec/support/max_staleness/ReplicaSetWithPrimary/ShortHeartbeartShortMaxStaleness.yml +29 -0
  156. data/spec/support/max_staleness/ReplicaSetWithPrimary/ShortHeartbeartShortMaxStaleness2.yml +29 -0
  157. data/spec/support/max_staleness/ReplicaSetWithPrimary/ZeroMaxStaleness.yml +27 -0
  158. data/spec/support/max_staleness/Sharded/Incompatible.yml +25 -0
  159. data/spec/support/max_staleness/Sharded/SmallMaxStaleness.yml +20 -0
  160. data/spec/support/max_staleness/Single/Incompatible.yml +18 -0
  161. data/spec/support/max_staleness/Single/SmallMaxStaleness.yml +20 -0
  162. data/spec/support/server_selection.rb +25 -0
  163. data/spec/support/server_selection/selection/ReplicaSetNoPrimary/read/Nearest_multiple.yml +27 -0
  164. data/spec/support/server_selection/selection/ReplicaSetNoPrimary/read/Secondary_multi_tags.yml +31 -0
  165. data/spec/support/server_selection/selection/ReplicaSetNoPrimary/read/Secondary_multi_tags2.yml +31 -0
  166. data/spec/support/server_selection/selection/ReplicaSetWithPrimary/read/Nearest_multiple.yml +34 -0
  167. data/spec/support/server_selection/selection/ReplicaSetWithPrimary/read/SecondaryPreferred_tags.yml +28 -0
  168. data/spec/support/shared/server_selector.rb +4 -3
  169. metadata +91 -6
  170. metadata.gz.sig +0 -0
@@ -43,7 +43,8 @@ module Mongo
43
43
  :features,
44
44
  :max_bson_object_size,
45
45
  :max_message_size,
46
- :mongos?
46
+ :mongos?,
47
+ :app_metadata
47
48
 
48
49
  # Tell the underlying socket to establish a connection to the host.
49
50
  #
@@ -60,6 +61,7 @@ module Mongo
60
61
  unless socket && socket.connectable?
61
62
  @socket = address.socket(timeout, ssl_options)
62
63
  socket.connect!
64
+ handshake!
63
65
  authenticate!
64
66
  end
65
67
  true
@@ -79,6 +81,7 @@ module Mongo
79
81
  def disconnect!
80
82
  if socket
81
83
  socket.close
84
+ @auth_mechanism = nil
82
85
  @socket = nil
83
86
  end
84
87
  true
@@ -130,6 +133,7 @@ module Mongo
130
133
  @server = server
131
134
  @ssl_options = options.reject { |k, v| !k.to_s.start_with?(SSL) }
132
135
  @socket = nil
136
+ @auth_mechanism = nil
133
137
  @pid = Process.pid
134
138
  end
135
139
 
@@ -159,6 +163,17 @@ module Mongo
159
163
  messages.last.replyable? ? read(messages.last.request_id) : nil
160
164
  end
161
165
 
166
+ def handshake!
167
+ if socket && socket.connectable?
168
+ socket.write(app_metadata.ismaster_bytes)
169
+ response = Protocol::Reply.deserialize(socket, max_message_size).documents[0]
170
+ min_wire_version = response[Description::MIN_WIRE_VERSION] || Description::LEGACY_WIRE_VERSION
171
+ max_wire_version = response[Description::MAX_WIRE_VERSION] || Description::LEGACY_WIRE_VERSION
172
+ features = Description::Features.new(min_wire_version..max_wire_version)
173
+ @auth_mechanism = (features.scram_sha_1_enabled? || @server.features.scram_sha_1_enabled?) ? :scram : :mongodb_cr
174
+ end
175
+ end
176
+
162
177
  def authenticate!
163
178
  if options[:user]
164
179
  user = Auth::User.new(Options::Redacted.new(:auth_mech => default_mechanism).merge(options))
@@ -169,16 +184,7 @@ module Mongo
169
184
  end
170
185
 
171
186
  def default_mechanism
172
- if socket && socket.connectable?
173
- socket.write(Monitor::Connection::ISMASTER_BYTES)
174
- ismaster = Protocol::Reply.deserialize(socket, max_message_size).documents[0]
175
- min_wire_version = ismaster[Description::MIN_WIRE_VERSION] || Description::LEGACY_WIRE_VERSION
176
- max_wire_version = ismaster[Description::MAX_WIRE_VERSION] || Description::LEGACY_WIRE_VERSION
177
- features = Description::Features.new(min_wire_version..max_wire_version)
178
- (features.scram_sha_1_enabled? || @server.features.scram_sha_1_enabled?) ? :scram : :mongodb_cr
179
- else
180
- @server.features.scram_sha_1_enabled? ? :scram : :mongodb_cr
181
- end
187
+ @auth_mechanism || (@server.features.scram_sha_1_enabled? ? :scram : :mongodb_cr)
182
188
  end
183
189
 
184
190
  def write(messages, buffer = BSON::ByteBuffer.new)
@@ -84,6 +84,16 @@ module Mongo
84
84
  # @since 2.0.0
85
85
  MAX_WRITE_BATCH_SIZE = 'maxWriteBatchSize'.freeze
86
86
 
87
+ # Constant for the lastWrite subdocument.
88
+ #
89
+ # @since 2.4.0
90
+ LAST_WRITE = 'lastWrite'.freeze
91
+
92
+ # Constant for the lastWriteDate field in the lastWrite subdocument.
93
+ #
94
+ # @since 2.4.0
95
+ LAST_WRITE_DATE = 'lastWriteDate'.freeze
96
+
87
97
  # Constant for reading the me field.
88
98
  #
89
99
  # @since 2.1.0
@@ -360,6 +370,18 @@ module Mongo
360
370
  config[SET_VERSION]
361
371
  end
362
372
 
373
+ # Get the lastWriteDate from the lastWrite subdocument in the config.
374
+ #
375
+ # @example Get the lastWriteDate value.
376
+ # description.last_write_date
377
+ #
378
+ # @return [ Time ] The last write date.
379
+ #
380
+ # @since 2.4.0
381
+ def last_write_date
382
+ config[LAST_WRITE][LAST_WRITE_DATE] if config[LAST_WRITE]
383
+ end
384
+
363
385
  # Is the server a mongos?
364
386
  #
365
387
  # @example Is the server a mongos?
@@ -25,6 +25,8 @@ module Mongo
25
25
  #
26
26
  # @since 2.0.0
27
27
  MAPPINGS = {
28
+ :collation => 5,
29
+ :max_staleness => 5,
28
30
  :find_command => 4,
29
31
  :list_collections => 3,
30
32
  :list_indexes => 3,
@@ -53,6 +53,11 @@ module Mongo
53
53
  # @return [ Hash ] options The server options.
54
54
  attr_reader :options
55
55
 
56
+ # @return [ Time ] last_scan The time of the last server scan.
57
+ #
58
+ # @since 2.4.0
59
+ attr_reader :last_scan
60
+
56
61
  # Force the monitor to immediately do a check of its server.
57
62
  #
58
63
  # @example Force a scan.
@@ -75,6 +75,7 @@ module Mongo
75
75
  unless socket && socket.connectable?
76
76
  @socket = address.socket(timeout, ssl_options)
77
77
  socket.connect!
78
+ handshake!
78
79
  end
79
80
  true
80
81
  end
@@ -115,6 +116,7 @@ module Mongo
115
116
  def initialize(address, options = {})
116
117
  @address = address
117
118
  @options = options.freeze
119
+ @app_metadata = options[:app_metadata]
118
120
  @ssl_options = options.reject { |k, v| !k.to_s.start_with?(SSL) }
119
121
  @socket = nil
120
122
  @pid = Process.pid
@@ -131,6 +133,15 @@ module Mongo
131
133
  def timeout
132
134
  @timeout ||= options[:connect_timeout] || CONNECT_TIMEOUT
133
135
  end
136
+
137
+ private
138
+
139
+ def handshake!
140
+ if @app_metadata
141
+ socket.write(@app_metadata.ismaster_bytes)
142
+ Protocol::Reply.deserialize(socket, Mongo::Protocol::Message::MAX_MESSAGE_SIZE).documents[0]
143
+ end
144
+ end
134
145
  end
135
146
  end
136
147
  end
@@ -67,6 +67,7 @@ module Mongo
67
67
  def to_mongos
68
68
  preference = { :mode => 'nearest' }
69
69
  preference.merge!({ :tags => tag_sets }) unless tag_sets.empty?
70
+ preference.merge!({ maxStalenessMS: max_staleness * 1000 }) if max_staleness
70
71
  preference
71
72
  end
72
73
 
@@ -76,18 +77,20 @@ module Mongo
76
77
  # local threshold between the nearest server and other servers.
77
78
  #
78
79
  # @example Select nearest servers given a list of candidates.
79
- # preference = Mongo::Serverreference::Nearest.new
80
+ # preference = Mongo::ServerSelector::Nearest.new
80
81
  # preference.select_server(cluster)
81
82
  #
82
83
  # @return [ Array ] The nearest servers from the list of candidates.
83
84
  #
84
85
  # @since 2.0.0
85
86
  def select(candidates)
86
- if tag_sets.empty?
87
- near_servers(candidates)
88
- else
89
- near_servers(match_tag_sets(candidates))
90
- end
87
+ matching_servers = filter_stale_servers(candidates, primary(candidates).first)
88
+ matching_servers = match_tag_sets(matching_servers) unless tag_sets.empty?
89
+ near_servers(matching_servers)
90
+ end
91
+
92
+ def max_staleness_allowed?
93
+ true
91
94
  end
92
95
  end
93
96
  end
@@ -83,6 +83,10 @@ module Mongo
83
83
  def select(candidates)
84
84
  primary(candidates)
85
85
  end
86
+
87
+ def max_staleness_allowed?
88
+ false
89
+ end
86
90
  end
87
91
  end
88
92
  end
@@ -68,6 +68,7 @@ module Mongo
68
68
  def to_mongos
69
69
  preference = { :mode => 'primaryPreferred' }
70
70
  preference.merge!({ :tags => tag_sets }) unless tag_sets.empty?
71
+ preference.merge!({ maxStalenessMS: max_staleness * 1000 }) if max_staleness
71
72
  preference
72
73
  end
73
74
 
@@ -87,7 +88,12 @@ module Mongo
87
88
  # @since 2.0.0
88
89
  def select(candidates)
89
90
  primary = primary(candidates)
90
- primary.first ? primary : near_servers(secondaries(candidates))
91
+ secondaries = near_servers(secondaries(candidates))
92
+ primary.first ? primary : secondaries
93
+ end
94
+
95
+ def max_staleness_allowed?
96
+ true
91
97
  end
92
98
  end
93
99
  end
@@ -68,6 +68,7 @@ module Mongo
68
68
  def to_mongos
69
69
  preference = { :mode => 'secondary' }
70
70
  preference.merge!({ :tags => tag_sets }) unless tag_sets.empty?
71
+ preference.merge!({ maxStalenessMS: max_staleness * 1000 }) if max_staleness
71
72
  preference
72
73
  end
73
74
 
@@ -86,6 +87,10 @@ module Mongo
86
87
  def select(candidates)
87
88
  near_servers(secondaries(candidates))
88
89
  end
90
+
91
+ def max_staleness_allowed?
92
+ true
93
+ end
89
94
  end
90
95
  end
91
96
  end
@@ -68,9 +68,10 @@ module Mongo
68
68
  #
69
69
  # @since 2.0.0
70
70
  def to_mongos
71
- return nil if tag_sets.empty?
71
+ return nil if tag_sets.empty? && max_staleness.nil?
72
72
  preference = { mode: 'secondaryPreferred' }
73
- preference.merge!({ tags: tag_sets })
73
+ preference.merge!({ tags: tag_sets }) unless tag_sets.empty?
74
+ preference.merge!({ maxStalenessMS: max_staleness * 1000 }) if max_staleness
74
75
  preference
75
76
  end
76
77
 
@@ -91,6 +92,10 @@ module Mongo
91
92
  def select(candidates)
92
93
  near_servers(secondaries(candidates)) + primary(candidates)
93
94
  end
95
+
96
+ def max_staleness_allowed?
97
+ true
98
+ end
94
99
  end
95
100
  end
96
101
  end
@@ -26,6 +26,12 @@ module Mongo
26
26
  # @return [ Array ] tag_sets The tag sets used to select servers.
27
27
  attr_reader :tag_sets
28
28
 
29
+ # @return [ Float ] max_staleness The maximum replication lag, in seconds, that a
30
+ # secondary can suffer and still be eligible for a read.
31
+ #
32
+ # @since 2.4.0
33
+ attr_reader :max_staleness
34
+
29
35
  # Check equality of two server selector.
30
36
  #
31
37
  # @example Check server selector equality.
@@ -38,7 +44,8 @@ module Mongo
38
44
  # @since 2.0.0
39
45
  def ==(other)
40
46
  name == other.name &&
41
- tag_sets == other.tag_sets
47
+ tag_sets == other.tag_sets &&
48
+ max_staleness == other.max_staleness
42
49
  end
43
50
 
44
51
  # Initialize the server selector.
@@ -60,9 +67,9 @@ module Mongo
60
67
  # @since 2.0.0
61
68
  def initialize(options = {})
62
69
  @options = (options || {}).freeze
63
- tag_sets = options[:tag_sets] || []
64
- validate_tag_sets!(tag_sets)
65
- @tag_sets = tag_sets.freeze
70
+ @tag_sets = (options[:tag_sets] || []).freeze
71
+ @max_staleness = options[:max_staleness] if options[:max_staleness] && options[:max_staleness] > 0
72
+ validate!
66
73
  end
67
74
 
68
75
  # Inspect the server selector.
@@ -74,7 +81,7 @@ module Mongo
74
81
  #
75
82
  # @since 2.2.0
76
83
  def inspect
77
- "#<#{self.class.name}:0x#{object_id} tag_sets=#{tag_sets.inspect}>"
84
+ "#<#{self.class.name}:0x#{object_id} tag_sets=#{tag_sets.inspect} max_staleness=#{max_staleness.inspect}>"
78
85
  end
79
86
 
80
87
  # Select a server from eligible candidates.
@@ -143,10 +150,11 @@ module Mongo
143
150
 
144
151
  def candidates(cluster)
145
152
  if cluster.single?
146
- cluster.servers
153
+ cluster.servers.each { |server| validate_max_staleness_support!(server) }
147
154
  elsif cluster.sharded?
148
- near_servers(cluster.servers)
155
+ near_servers(cluster.servers).each { |server| validate_max_staleness_support!(server) }
149
156
  else
157
+ validate_max_staleness_value!(cluster)
150
158
  select(cluster.servers)
151
159
  end
152
160
  end
@@ -175,6 +183,7 @@ module Mongo
175
183
  # @since 2.0.0
176
184
  def secondaries(candidates)
177
185
  matching_servers = candidates.select(&:secondary?)
186
+ matching_servers = filter_stale_servers(matching_servers, primary(candidates).first)
178
187
  matching_servers = match_tag_sets(matching_servers) unless tag_sets.empty?
179
188
  matching_servers
180
189
  end
@@ -212,9 +221,47 @@ module Mongo
212
221
  matches || []
213
222
  end
214
223
 
215
- def validate_tag_sets!(tag_sets)
216
- if !tag_sets.all? { |set| set.empty? } && !tags_allowed?
217
- raise Error::InvalidServerPreference.new(name)
224
+ def filter_stale_servers(candidates, primary = nil)
225
+ return candidates unless @max_staleness
226
+ max_staleness_ms = @max_staleness * 1000
227
+
228
+ if primary
229
+ candidates.select do |server|
230
+ validate_max_staleness_support!(server)
231
+ staleness = (server.last_scan - server.last_write_date) -
232
+ (primary.last_scan - primary.last_write_date) +
233
+ (server.heartbeat_frequency * 1000)
234
+ staleness <= max_staleness_ms
235
+ end
236
+ else
237
+ max_write_date = candidates.collect(&:last_write_date).max
238
+ candidates.select do |server|
239
+ validate_max_staleness_support!(server)
240
+ staleness = max_write_date - server.last_write_date + (server.heartbeat_frequency * 1000)
241
+ staleness <= max_staleness_ms
242
+ end
243
+ end
244
+ end
245
+
246
+ def validate!
247
+ if !@tag_sets.all? { |set| set.empty? } && !tags_allowed?
248
+ raise Error::InvalidServerPreference.new(Error::InvalidServerPreference::NO_TAG_SUPPORT)
249
+ elsif @max_staleness && !max_staleness_allowed?
250
+ raise Error::InvalidServerPreference.new(Error::InvalidServerPreference::NO_MAX_STALENESS_SUPPORT)
251
+ end
252
+ end
253
+
254
+ def validate_max_staleness_support!(server)
255
+ if @max_staleness && !server.features.max_staleness_enabled?
256
+ raise Error::InvalidServerPreference.new(Error::InvalidServerPreference::NO_MAX_STALENESS_WITH_LEGACY_SERVER)
257
+ end
258
+ end
259
+
260
+ def validate_max_staleness_value!(cluster)
261
+ return unless @max_staleness
262
+ heartbeat_frequency = cluster.options[:heartbeat_frequency] || Server::Monitor::HEARTBEAT_FREQUENCY
263
+ if @max_staleness < heartbeat_frequency * 2
264
+ raise Error::InvalidServerPreference.new(Error::InvalidServerPreference::INVALID_MAX_STALENESS)
218
265
  end
219
266
  end
220
267
  end
@@ -56,6 +56,7 @@ module Mongo
56
56
  Timeout.timeout(timeout, Error::SocketTimeoutError) do
57
57
  handle_errors { @tcp_socket.connect(::Socket.pack_sockaddr_in(port, host)) }
58
58
  @socket = OpenSSL::SSL::SSLSocket.new(@tcp_socket, context)
59
+ @socket.hostname = @host_name unless BSON::Environment.jruby?
59
60
  @socket.sync_close = true
60
61
  handle_errors { @socket.connect }
61
62
  verify_certificate!(@socket)
@@ -374,6 +374,7 @@ module Mongo
374
374
  # Read Options
375
375
  uri_option 'readpreference', :mode, :group => :read, :type => :read_mode
376
376
  uri_option 'readpreferencetags', :tag_sets, :group => :read, :type => :read_tags
377
+ uri_option 'maxstalenessms', :max_staleness, :group => :read, :type => :ms_convert
377
378
 
378
379
  # Pool options
379
380
  uri_option 'minpoolsize', :min_pool_size
@@ -391,6 +392,9 @@ module Mongo
391
392
  uri_option 'authmechanism', :auth_mech, :type => :auth_mech
392
393
  uri_option 'authmechanismproperties', :auth_mech_properties, :type => :auth_mech_props
393
394
 
395
+ # Client Options
396
+ uri_option 'appname', :app_name
397
+
394
398
  # Casts option values that do not have a specifically provided
395
399
  # transformation to the appropriate type.
396
400
  #
@@ -17,5 +17,5 @@ module Mongo
17
17
  # The current version of the driver.
18
18
  #
19
19
  # @since 2.0.0
20
- VERSION = '2.3.1'.freeze
20
+ VERSION = '2.4.0.rc0'.freeze
21
21
  end
@@ -76,6 +76,7 @@ module Mongo
76
76
  #
77
77
  # @since 2.0.0
78
78
  def get(options)
79
+ return options if options.is_a?(Unacknowledged) || options.is_a?(Acknowledged)
79
80
  if options
80
81
  validate!(options)
81
82
  if unacknowledged?(options)
@@ -30,5 +30,5 @@ Gem::Specification.new do |s|
30
30
  s.has_rdoc = 'yard'
31
31
  s.bindir = 'bin'
32
32
 
33
- s.add_dependency 'bson', '~> 4.1'
33
+ s.add_dependency 'bson', '~> 4.2.0.rc0'
34
34
  end
@@ -14,8 +14,14 @@ describe Mongo::Auth::CR do
14
14
  Mongo::Event::Listeners.new
15
15
  end
16
16
 
17
+ let(:cluster) do
18
+ double('cluster').tap do |cl|
19
+ allow(cl).to receive(:app_metadata).and_return(app_metadata)
20
+ end
21
+ end
22
+
17
23
  let(:server) do
18
- Mongo::Server.new(address, double('cluster'), monitoring, listeners, TEST_OPTIONS)
24
+ Mongo::Server.new(address, cluster, monitoring, listeners, TEST_OPTIONS)
19
25
  end
20
26
 
21
27
  let(:connection) do