mongo 2.3.1 → 2.4.0.rc0

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