mongo 1.3.0 → 1.12.5

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 (185) hide show
  1. checksums.yaml +7 -0
  2. checksums.yaml.gz.sig +0 -0
  3. data/{LICENSE.txt → LICENSE} +1 -1
  4. data/README.md +122 -271
  5. data/Rakefile +25 -209
  6. data/VERSION +1 -0
  7. data/bin/mongo_console +31 -9
  8. data/lib/mongo/bulk_write_collection_view.rb +387 -0
  9. data/lib/mongo/collection.rb +576 -269
  10. data/lib/mongo/collection_writer.rb +364 -0
  11. data/lib/mongo/connection/node.rb +249 -0
  12. data/lib/mongo/connection/pool.rb +340 -0
  13. data/lib/mongo/connection/pool_manager.rb +320 -0
  14. data/lib/mongo/connection/sharding_pool_manager.rb +67 -0
  15. data/lib/mongo/connection/socket/socket_util.rb +37 -0
  16. data/lib/mongo/connection/socket/ssl_socket.rb +95 -0
  17. data/lib/mongo/connection/socket/tcp_socket.rb +87 -0
  18. data/lib/mongo/connection/socket/unix_socket.rb +39 -0
  19. data/lib/mongo/connection/socket.rb +18 -0
  20. data/lib/mongo/connection.rb +7 -875
  21. data/lib/mongo/cursor.rb +403 -117
  22. data/lib/mongo/db.rb +444 -243
  23. data/lib/mongo/exception.rb +145 -0
  24. data/lib/mongo/functional/authentication.rb +455 -0
  25. data/lib/mongo/functional/logging.rb +85 -0
  26. data/lib/mongo/functional/read_preference.rb +183 -0
  27. data/lib/mongo/functional/scram.rb +556 -0
  28. data/lib/mongo/functional/uri_parser.rb +409 -0
  29. data/lib/mongo/functional/write_concern.rb +66 -0
  30. data/lib/mongo/functional.rb +20 -0
  31. data/lib/mongo/gridfs/grid.rb +30 -24
  32. data/lib/mongo/gridfs/grid_ext.rb +6 -10
  33. data/lib/mongo/gridfs/grid_file_system.rb +38 -20
  34. data/lib/mongo/gridfs/grid_io.rb +84 -75
  35. data/lib/mongo/gridfs.rb +18 -0
  36. data/lib/mongo/legacy.rb +140 -0
  37. data/lib/mongo/mongo_client.rb +697 -0
  38. data/lib/mongo/mongo_replica_set_client.rb +535 -0
  39. data/lib/mongo/mongo_sharded_client.rb +159 -0
  40. data/lib/mongo/networking.rb +372 -0
  41. data/lib/mongo/{util → utils}/conversions.rb +29 -8
  42. data/lib/mongo/{util → utils}/core_ext.rb +28 -18
  43. data/lib/mongo/{util → utils}/server_version.rb +4 -6
  44. data/lib/mongo/{util → utils}/support.rb +29 -31
  45. data/lib/mongo/utils/thread_local_variable_manager.rb +25 -0
  46. data/lib/mongo/utils.rb +19 -0
  47. data/lib/mongo.rb +51 -50
  48. data/mongo.gemspec +29 -32
  49. data/test/functional/authentication_test.rb +39 -0
  50. data/test/functional/bulk_api_stress_test.rb +133 -0
  51. data/test/functional/bulk_write_collection_view_test.rb +1198 -0
  52. data/test/functional/client_test.rb +627 -0
  53. data/test/functional/collection_test.rb +2175 -0
  54. data/test/functional/collection_writer_test.rb +83 -0
  55. data/test/{conversions_test.rb → functional/conversions_test.rb} +47 -3
  56. data/test/functional/cursor_fail_test.rb +57 -0
  57. data/test/functional/cursor_message_test.rb +56 -0
  58. data/test/functional/cursor_test.rb +683 -0
  59. data/test/functional/db_api_test.rb +835 -0
  60. data/test/functional/db_connection_test.rb +25 -0
  61. data/test/functional/db_test.rb +348 -0
  62. data/test/functional/grid_file_system_test.rb +285 -0
  63. data/test/{grid_io_test.rb → functional/grid_io_test.rb} +72 -11
  64. data/test/{grid_test.rb → functional/grid_test.rb} +88 -15
  65. data/test/functional/pool_test.rb +136 -0
  66. data/test/functional/safe_test.rb +98 -0
  67. data/test/functional/ssl_test.rb +29 -0
  68. data/test/functional/support_test.rb +62 -0
  69. data/test/functional/timeout_test.rb +60 -0
  70. data/test/functional/uri_test.rb +446 -0
  71. data/test/functional/write_concern_test.rb +118 -0
  72. data/test/helpers/general.rb +50 -0
  73. data/test/helpers/test_unit.rb +476 -0
  74. data/test/replica_set/authentication_test.rb +37 -0
  75. data/test/replica_set/basic_test.rb +189 -0
  76. data/test/replica_set/client_test.rb +393 -0
  77. data/test/replica_set/connection_test.rb +138 -0
  78. data/test/replica_set/count_test.rb +66 -0
  79. data/test/replica_set/cursor_test.rb +220 -0
  80. data/test/replica_set/insert_test.rb +157 -0
  81. data/test/replica_set/max_values_test.rb +151 -0
  82. data/test/replica_set/pinning_test.rb +105 -0
  83. data/test/replica_set/query_test.rb +73 -0
  84. data/test/replica_set/read_preference_test.rb +219 -0
  85. data/test/replica_set/refresh_test.rb +211 -0
  86. data/test/replica_set/replication_ack_test.rb +95 -0
  87. data/test/replica_set/ssl_test.rb +32 -0
  88. data/test/sharded_cluster/basic_test.rb +203 -0
  89. data/test/shared/authentication/basic_auth_shared.rb +260 -0
  90. data/test/shared/authentication/bulk_api_auth_shared.rb +249 -0
  91. data/test/shared/authentication/gssapi_shared.rb +176 -0
  92. data/test/shared/authentication/sasl_plain_shared.rb +96 -0
  93. data/test/shared/authentication/scram_shared.rb +92 -0
  94. data/test/shared/ssl_shared.rb +235 -0
  95. data/test/test_helper.rb +53 -94
  96. data/test/threading/basic_test.rb +120 -0
  97. data/test/tools/mongo_config.rb +708 -0
  98. data/test/tools/mongo_config_test.rb +160 -0
  99. data/test/unit/client_test.rb +381 -0
  100. data/test/unit/collection_test.rb +89 -53
  101. data/test/unit/connection_test.rb +282 -32
  102. data/test/unit/cursor_test.rb +206 -8
  103. data/test/unit/db_test.rb +55 -13
  104. data/test/unit/grid_test.rb +43 -16
  105. data/test/unit/mongo_sharded_client_test.rb +48 -0
  106. data/test/unit/node_test.rb +93 -0
  107. data/test/unit/pool_manager_test.rb +111 -0
  108. data/test/unit/read_pref_test.rb +406 -0
  109. data/test/unit/read_test.rb +159 -0
  110. data/test/unit/safe_test.rb +69 -36
  111. data/test/unit/sharding_pool_manager_test.rb +84 -0
  112. data/test/unit/write_concern_test.rb +175 -0
  113. data.tar.gz.sig +3 -0
  114. metadata +227 -216
  115. metadata.gz.sig +0 -0
  116. data/docs/CREDITS.md +0 -123
  117. data/docs/FAQ.md +0 -116
  118. data/docs/GridFS.md +0 -158
  119. data/docs/HISTORY.md +0 -244
  120. data/docs/RELEASES.md +0 -33
  121. data/docs/REPLICA_SETS.md +0 -72
  122. data/docs/TUTORIAL.md +0 -247
  123. data/docs/WRITE_CONCERN.md +0 -28
  124. data/lib/mongo/exceptions.rb +0 -71
  125. data/lib/mongo/gridfs/grid_io_fix.rb +0 -38
  126. data/lib/mongo/repl_set_connection.rb +0 -342
  127. data/lib/mongo/test.rb +0 -20
  128. data/lib/mongo/util/pool.rb +0 -177
  129. data/lib/mongo/util/uri_parser.rb +0 -185
  130. data/test/async/collection_test.rb +0 -224
  131. data/test/async/connection_test.rb +0 -24
  132. data/test/async/cursor_test.rb +0 -162
  133. data/test/async/worker_pool_test.rb +0 -99
  134. data/test/auxillary/1.4_features.rb +0 -166
  135. data/test/auxillary/authentication_test.rb +0 -68
  136. data/test/auxillary/autoreconnect_test.rb +0 -41
  137. data/test/auxillary/fork_test.rb +0 -30
  138. data/test/auxillary/repl_set_auth_test.rb +0 -58
  139. data/test/auxillary/slave_connection_test.rb +0 -36
  140. data/test/auxillary/threaded_authentication_test.rb +0 -101
  141. data/test/bson/binary_test.rb +0 -15
  142. data/test/bson/bson_test.rb +0 -649
  143. data/test/bson/byte_buffer_test.rb +0 -208
  144. data/test/bson/hash_with_indifferent_access_test.rb +0 -38
  145. data/test/bson/json_test.rb +0 -17
  146. data/test/bson/object_id_test.rb +0 -154
  147. data/test/bson/ordered_hash_test.rb +0 -204
  148. data/test/bson/timestamp_test.rb +0 -24
  149. data/test/collection_test.rb +0 -910
  150. data/test/connection_test.rb +0 -309
  151. data/test/cursor_fail_test.rb +0 -75
  152. data/test/cursor_message_test.rb +0 -43
  153. data/test/cursor_test.rb +0 -483
  154. data/test/db_api_test.rb +0 -726
  155. data/test/db_connection_test.rb +0 -15
  156. data/test/db_test.rb +0 -287
  157. data/test/grid_file_system_test.rb +0 -243
  158. data/test/load/resque/load.rb +0 -21
  159. data/test/load/resque/processor.rb +0 -26
  160. data/test/load/thin/load.rb +0 -24
  161. data/test/load/unicorn/load.rb +0 -23
  162. data/test/load/unicorn/unicorn.rb +0 -29
  163. data/test/replica_sets/connect_test.rb +0 -94
  164. data/test/replica_sets/connection_string_test.rb +0 -32
  165. data/test/replica_sets/count_test.rb +0 -35
  166. data/test/replica_sets/insert_test.rb +0 -53
  167. data/test/replica_sets/pooled_insert_test.rb +0 -55
  168. data/test/replica_sets/query_secondaries.rb +0 -96
  169. data/test/replica_sets/query_test.rb +0 -51
  170. data/test/replica_sets/replication_ack_test.rb +0 -66
  171. data/test/replica_sets/rs_test_helper.rb +0 -27
  172. data/test/safe_test.rb +0 -68
  173. data/test/support/hash_with_indifferent_access.rb +0 -186
  174. data/test/support/keys.rb +0 -45
  175. data/test/support_test.rb +0 -18
  176. data/test/threading/threading_with_large_pool_test.rb +0 -90
  177. data/test/threading_test.rb +0 -87
  178. data/test/tools/auth_repl_set_manager.rb +0 -14
  179. data/test/tools/load.rb +0 -58
  180. data/test/tools/repl_set_manager.rb +0 -266
  181. data/test/tools/sharding_manager.rb +0 -202
  182. data/test/tools/test.rb +0 -4
  183. data/test/unit/pool_test.rb +0 -9
  184. data/test/unit/repl_set_connection_test.rb +0 -59
  185. data/test/uri_test.rb +0 -91
data/lib/mongo/cursor.rb CHANGED
@@ -1,12 +1,10 @@
1
- # encoding: UTF-8
2
-
3
- # Copyright (C) 2008-2011 10gen Inc.
1
+ # Copyright (C) 2009-2013 MongoDB, Inc.
4
2
  #
5
3
  # Licensed under the Apache License, Version 2.0 (the "License");
6
4
  # you may not use this file except in compliance with the License.
7
5
  # You may obtain a copy of the License at
8
6
  #
9
- # http://www.apache.org/licenses/LICENSE-2.0
7
+ # http://www.apache.org/licenses/LICENSE-2.0
10
8
  #
11
9
  # Unless required by applicable law or agreed to in writing, software
12
10
  # distributed under the License is distributed on an "AS IS" BASIS,
@@ -18,12 +16,17 @@ module Mongo
18
16
 
19
17
  # A cursor over query results. Returned objects are hashes.
20
18
  class Cursor
21
- include Mongo::Conversions
22
19
  include Enumerable
20
+ include Mongo::Constants
21
+ include Mongo::Conversions
22
+ include Mongo::Logging
23
+ include Mongo::ReadPreference
23
24
 
24
25
  attr_reader :collection, :selector, :fields,
25
- :order, :hint, :snapshot, :timeout,
26
- :full_collection_name, :transformer
26
+ :order, :hint, :snapshot, :timeout, :transformer,
27
+ :options, :cursor_id, :show_disk_loc,
28
+ :comment, :compile_regex, :read, :tag_sets,
29
+ :acceptable_latency
27
30
 
28
31
  # Create a new cursor.
29
32
  #
@@ -31,62 +34,132 @@ module Mongo
31
34
  # similar methods. Application developers shouldn't have to create cursors manually.
32
35
  #
33
36
  # @return [Cursor]
34
- #
35
- # @core cursors constructor_details
36
37
  def initialize(collection, opts={})
37
- @cursor_id = nil
38
-
38
+ opts = opts.dup
39
+ @cursor_id = opts.delete(:cursor_id)
39
40
  @db = collection.db
40
41
  @collection = collection
42
+ @ns = opts.delete(:ns)
41
43
  @connection = @db.connection
42
44
  @logger = @connection.logger
43
45
 
44
- @selector = opts[:selector] || {}
45
- @fields = convert_fields_for_query(opts[:fields])
46
- @skip = opts[:skip] || 0
47
- @limit = opts[:limit] || 0
48
- @order = opts[:order]
49
- @hint = opts[:hint]
50
- @snapshot = opts[:snapshot]
51
- @timeout = opts.fetch(:timeout, true)
52
- @explain = opts[:explain]
53
- @socket = opts[:socket]
54
- @tailable = opts[:tailable] || false
55
- @closed = false
56
- @query_run = false
57
- @transformer = opts[:transformer]
58
- batch_size(opts[:batch_size] || 0)
59
-
60
- @full_collection_name = "#{@collection.db.name}.#{@collection.name}"
61
- @cache = []
62
- @returned = 0
46
+ # Query selector
47
+ @selector = opts.delete(:selector) || {}
48
+
49
+ # Query pre-serialized bson to append
50
+ @bson = @selector.delete(:bson)
51
+
52
+ # Special operators that form part of $query
53
+ @order = opts.delete(:order)
54
+ @explain = opts.delete(:explain)
55
+ @hint = opts.delete(:hint)
56
+ @snapshot = opts.delete(:snapshot)
57
+ @max_scan = opts.delete(:max_scan)
58
+ @return_key = opts.delete(:return_key)
59
+ @show_disk_loc = opts.delete(:show_disk_loc)
60
+ @comment = opts.delete(:comment)
61
+ @compile_regex = opts.key?(:compile_regex) ? opts.delete(:compile_regex) : true
62
+
63
+ # Wire-protocol settings
64
+ @fields = convert_fields_for_query(opts.delete(:fields))
65
+ @skip = opts.delete(:skip) || 0
66
+ @limit = opts.delete(:limit) || 0
67
+ @tailable = opts.delete(:tailable)
68
+ @timeout = opts.key?(:timeout) ? opts.delete(:timeout) : true
69
+ @options = 0
70
+
71
+ # Use this socket for the query
72
+ @socket = opts.delete(:socket)
73
+ @pool = opts.delete(:pool)
74
+
75
+ @closed = false
76
+ @query_run = false
77
+
78
+ @transformer = opts.delete(:transformer)
79
+ @read = opts.delete(:read) || @collection.read
80
+ Mongo::ReadPreference::validate(@read)
81
+ @tag_sets = opts.delete(:tag_sets) || @collection.tag_sets
82
+ @acceptable_latency = opts.delete(:acceptable_latency) || @collection.acceptable_latency
83
+
84
+ batch_size(opts.delete(:batch_size) || 0)
85
+
86
+ @cache = opts.delete(:first_batch) || []
87
+ @returned = 0
88
+
89
+ if(!@timeout)
90
+ add_option(OP_QUERY_NO_CURSOR_TIMEOUT)
91
+ end
92
+ if(@read != :primary)
93
+ add_option(OP_QUERY_SLAVE_OK)
94
+ end
95
+ if(@tailable)
96
+ add_option(OP_QUERY_TAILABLE)
97
+ end
98
+
99
+ # If a cursor_id is provided, this is a cursor for a command
100
+ if @cursor_id
101
+ @command_cursor = true
102
+ @query_run = true
103
+ end
63
104
 
64
105
  if @collection.name =~ /^\$cmd/ || @collection.name =~ /^system/
65
106
  @command = true
66
107
  else
67
108
  @command = false
68
109
  end
110
+
111
+ @opts = opts
112
+ end
113
+
114
+ # Guess whether the cursor is alive on the server.
115
+ #
116
+ # Note that this method only checks whether we have
117
+ # a cursor id. The cursor may still have timed out
118
+ # on the server. This will be indicated in the next
119
+ # call to Cursor#next.
120
+ #
121
+ # @return [Boolean]
122
+ def alive?
123
+ @cursor_id && @cursor_id != 0
124
+ end
125
+
126
+ def full_collection_name
127
+ @ns || "#{@collection.db.name}.#{@collection.name}"
69
128
  end
70
129
 
71
130
  # Get the next document specified the cursor options.
72
131
  #
73
132
  # @return [Hash, Nil] the next document or Nil if no documents remain.
74
- def next_document
75
- refresh if @cache.length == 0
133
+ def next
134
+ if @cache.length == 0
135
+ if @query_run && exhaust?
136
+ close
137
+ return nil
138
+ else
139
+ refresh
140
+ end
141
+ end
76
142
  doc = @cache.shift
77
143
 
78
- if doc && doc['$err']
79
- err = doc['$err']
144
+ if doc && (err = doc['errmsg'] || doc['$err']) # assignment
145
+ code = doc['code'] || doc['assertionCode']
80
146
 
81
147
  # If the server has stopped being the master (e.g., it's one of a
82
148
  # pair but it has died or something like that) then we close that
83
149
  # connection. The next request will re-open on master server.
84
- if err == "not master"
150
+ if err.include?("not master")
85
151
  @connection.close
86
- raise ConnectionFailure, err
152
+ raise ConnectionFailure.new(err, code, doc)
153
+ end
154
+
155
+ # Handle server side operation execution timeout
156
+ if code == 50
157
+ raise ExecutionTimeout.new(err, code, doc)
87
158
  end
88
159
 
89
- raise OperationFailure, err
160
+ raise OperationFailure.new(err, code, doc)
161
+ elsif doc && (write_concern_error = doc['writeConcernError']) # assignment
162
+ raise WriteConcernError.new(write_concern_error['errmsg'], write_concern_error['code'], doc)
90
163
  end
91
164
 
92
165
  if @transformer.nil?
@@ -95,11 +168,12 @@ module Mongo
95
168
  @transformer.call(doc) if doc
96
169
  end
97
170
  end
98
- alias :next :next_document
171
+ alias :next_document :next
99
172
 
100
173
  # Reset this cursor on the server. Cursor options, such as the
101
174
  # query string and the values for skip and limit, are preserved.
102
175
  def rewind!
176
+ check_command_cursor
103
177
  close
104
178
  @cache.clear
105
179
  @cursor_id = nil
@@ -118,12 +192,13 @@ module Mongo
118
192
 
119
193
  # Get the size of the result set for this query.
120
194
  #
121
- # @param [Boolean] whether of not to take notice of skip and limit
195
+ # @param [Boolean] skip_and_limit whether or not to take skip or limit into account.
122
196
  #
123
197
  # @return [Integer] the number of objects in the result set for this query.
124
198
  #
125
199
  # @raise [OperationFailure] on a database error.
126
200
  def count(skip_and_limit = false)
201
+ check_command_cursor
127
202
  command = BSON::OrderedHash["count", @collection.name, "query", @selector]
128
203
 
129
204
  if skip_and_limit
@@ -131,12 +206,17 @@ module Mongo
131
206
  command.merge!(BSON::OrderedHash["skip", @skip]) if @skip != 0
132
207
  end
133
208
 
209
+ if @hint
210
+ hint = @hint.is_a?(String) ? @hint : generate_index_name(@hint)
211
+ end
212
+
134
213
  command.merge!(BSON::OrderedHash["fields", @fields])
214
+ command.merge!(BSON::OrderedHash["hint", hint]) if hint
135
215
 
136
- response = @db.command(command)
216
+ response = @db.command(command, :read => @read, :comment => @comment)
137
217
  return response['n'].to_i if Mongo::Support.ok?(response)
138
218
  return 0 if response['errmsg'] == "ns missing"
139
- raise OperationFailure, "Count failed: #{response['errmsg']}"
219
+ raise OperationFailure.new("Count failed: #{response['errmsg']}", response['code'], response)
140
220
  end
141
221
 
142
222
  # Sort this cursor's results.
@@ -144,22 +224,18 @@ module Mongo
144
224
  # This method overrides any sort order specified in the Collection#find
145
225
  # method, and only the last sort applied has an effect.
146
226
  #
147
- # @param [Symbol, Array] key_or_list either 1) a key to sort by or 2)
148
- # an array of [key, direction] pairs to sort by. Direction should
149
- # be specified as Mongo::ASCENDING (or :ascending / :asc) or Mongo::DESCENDING (or :descending / :desc)
227
+ # @param [Symbol, Array, Hash, OrderedHash] order either 1) a key to sort by 2)
228
+ # an array of [key, direction] pairs to sort by or 3) a hash of
229
+ # field => direction pairs to sort by. Direction should be specified as
230
+ # Mongo::ASCENDING (or :ascending / :asc) or Mongo::DESCENDING
231
+ # (or :descending / :desc)
150
232
  #
151
233
  # @raise [InvalidOperation] if this cursor has already been used.
152
234
  #
153
235
  # @raise [InvalidSortValueError] if the specified order is invalid.
154
- def sort(key_or_list, direction=nil)
236
+ def sort(order, direction=nil)
155
237
  check_modifiable
156
-
157
- if !direction.nil?
158
- order = [[key_or_list, direction]]
159
- else
160
- order = key_or_list
161
- end
162
-
238
+ order = [[order, direction]] unless direction.nil?
163
239
  @order = order
164
240
  self
165
241
  end
@@ -172,12 +248,14 @@ module Mongo
172
248
  # @return [Integer] the current number_to_return if no parameter is given.
173
249
  #
174
250
  # @raise [InvalidOperation] if this cursor has already been used.
175
- #
176
- # @core limit limit-instance_method
177
251
  def limit(number_to_return=nil)
178
252
  return @limit unless number_to_return
179
253
  check_modifiable
180
254
 
255
+ if (number_to_return != 0) && exhaust?
256
+ raise MongoArgumentError, "Limit is incompatible with exhaust option."
257
+ end
258
+
181
259
  @limit = number_to_return
182
260
  self
183
261
  end
@@ -199,28 +277,57 @@ module Mongo
199
277
  self
200
278
  end
201
279
 
280
+ # Instruct the server to abort queries after they exceed the specified
281
+ # wall-clock execution time.
282
+ #
283
+ # A query that completes in under its time limit will "roll over"
284
+ # remaining time to the first getmore op (which will then "roll over"
285
+ # its remaining time to the second getmore op and so on, until the
286
+ # time limit is hit).
287
+ #
288
+ # Cursors returned by successful time-limited queries will still obey
289
+ # the default cursor idle timeout (unless the "no cursor idle timeout"
290
+ # flag has been set).
291
+ #
292
+ # @note This will only have an effect in MongoDB 2.5+
293
+ #
294
+ # @param max_time_ms [Fixnum] max execution time (in milliseconds)
295
+ #
296
+ # @return [Fixnum, Cursor] either the current max_time_ms or cursor
297
+ def max_time_ms(max_time_ms=nil)
298
+ return @max_time_ms unless max_time_ms
299
+ check_modifiable
300
+
301
+ @max_time_ms = max_time_ms
302
+ self
303
+ end
304
+
202
305
  # Set the batch size for server responses.
203
306
  #
204
307
  # Note that the batch size will take effect only on queries
205
308
  # where the number to be returned is greater than 100.
206
309
  #
310
+ # This can not override MongoDB's limit on the amount of data it will
311
+ # return to the client. Depending on server version this can be 4-16mb.
312
+ #
207
313
  # @param [Integer] size either 0 or some integer greater than 1. If 0,
208
314
  # the server will determine the batch size.
209
315
  #
210
316
  # @return [Cursor]
211
- def batch_size(size=0)
317
+ def batch_size(size=nil)
318
+ return @batch_size unless size
212
319
  check_modifiable
213
320
  if size < 0 || size == 1
214
321
  raise ArgumentError, "Invalid value for batch_size #{size}; must be 0 or > 1."
215
322
  else
216
- @batch_size = size > @limit ? @limit : size
323
+ @batch_size = @limit != 0 && size > @limit ? @limit : size
217
324
  end
218
325
 
219
326
  self
220
327
  end
221
328
 
222
329
  # Iterate over each document in this cursor, yielding it to the given
223
- # block.
330
+ # block, if provided. An Enumerator is returned if no block is given.
224
331
  #
225
332
  # Iterating over an entire cursor will close it.
226
333
  #
@@ -231,14 +338,18 @@ module Mongo
231
338
  # puts doc['user']
232
339
  # end
233
340
  def each
234
- #num_returned = 0
235
- #while has_next? && (@limit <= 0 || num_returned < @limit)
236
- while doc = next_document
237
- yield doc #next_document
238
- #num_returned += 1
341
+ if block_given? || !defined?(Enumerator)
342
+ while doc = self.next
343
+ yield doc
344
+ end
345
+ else
346
+ Enumerator.new do |yielder|
347
+ while doc = self.next
348
+ yielder.yield doc
349
+ end
350
+ end
239
351
  end
240
352
  end
241
-
242
353
  # Receive all the documents from this cursor as an array of hashes.
243
354
  #
244
355
  # Notes:
@@ -258,10 +369,10 @@ module Mongo
258
369
  # Get the explain plan for this cursor.
259
370
  #
260
371
  # @return [Hash] a document containing the explain plan for this cursor.
261
- #
262
- # @core explain explain-instance_method
263
372
  def explain
264
- c = Cursor.new(@collection, query_options_hash.merge(:limit => -@limit.abs, :explain => true))
373
+ check_command_cursor
374
+ c = Cursor.new(@collection,
375
+ query_options_hash.merge(:limit => -@limit.abs, :explain => true))
265
376
  explanation = c.next_document
266
377
  c.close
267
378
 
@@ -283,8 +394,12 @@ module Mongo
283
394
  message = BSON::ByteBuffer.new([0, 0, 0, 0])
284
395
  message.put_int(1)
285
396
  message.put_long(@cursor_id)
286
- @logger.debug("MONGODB cursor.close #{@cursor_id}") if @logger
287
- @connection.send_message(Mongo::Constants::OP_KILL_CURSORS, message, nil)
397
+ log(:debug, "Cursor#close #{@cursor_id}")
398
+ @connection.send_message(
399
+ Mongo::Constants::OP_KILL_CURSORS,
400
+ message,
401
+ :pool => @pool
402
+ )
288
403
  end
289
404
  @cursor_id = 0
290
405
  @closed = true
@@ -293,7 +408,9 @@ module Mongo
293
408
  # Is this cursor closed?
294
409
  #
295
410
  # @return [Boolean]
296
- def closed?; @closed; end
411
+ def closed?
412
+ @closed
413
+ end
297
414
 
298
415
  # Returns an integer indicating which query options have been selected.
299
416
  #
@@ -302,31 +419,76 @@ module Mongo
302
419
  # @see http://www.mongodb.org/display/DOCS/Mongo+Wire+Protocol#MongoWireProtocol-Mongo::Constants::OPQUERY
303
420
  # The MongoDB wire protocol.
304
421
  def query_opts
305
- opts = 0
306
- opts |= Mongo::Constants::OP_QUERY_NO_CURSOR_TIMEOUT unless @timeout
307
- opts |= Mongo::Constants::OP_QUERY_SLAVE_OK if @connection.slave_ok?
308
- opts |= Mongo::Constants::OP_QUERY_TAILABLE if @tailable
309
- opts
422
+ warn "The method Cursor#query_opts has been deprecated " +
423
+ "and will removed in v2.0. Use Cursor#options instead."
424
+ @options
425
+ end
426
+
427
+ # Add an option to the query options bitfield.
428
+ #
429
+ # @param opt a valid query option
430
+ #
431
+ # @raise InvalidOperation if this method is run after the cursor has bee
432
+ # iterated for the first time.
433
+ #
434
+ # @return [Integer] the current value of the options bitfield for this cursor.
435
+ #
436
+ # @see http://www.mongodb.org/display/DOCS/Mongo+Wire+Protocol#MongoWireProtocol-Mongo::Constants::OPQUERY
437
+ def add_option(opt)
438
+ check_modifiable
439
+
440
+ if exhaust?(opt)
441
+ if @limit != 0
442
+ raise MongoArgumentError, "Exhaust is incompatible with limit."
443
+ elsif @connection.mongos?
444
+ raise MongoArgumentError, "Exhaust is incompatible with mongos."
445
+ end
446
+ end
447
+
448
+ @options |= opt
449
+ @options
450
+ end
451
+
452
+ # Remove an option from the query options bitfield.
453
+ #
454
+ # @param opt a valid query option
455
+ #
456
+ # @raise InvalidOperation if this method is run after the cursor has bee
457
+ # iterated for the first time.
458
+ #
459
+ # @return [Integer] the current value of the options bitfield for this cursor.
460
+ #
461
+ # @see http://www.mongodb.org/display/DOCS/Mongo+Wire+Protocol#MongoWireProtocol-Mongo::Constants::OPQUERY
462
+ def remove_option(opt)
463
+ check_modifiable
464
+
465
+ @options &= ~opt
466
+ @options
310
467
  end
311
468
 
312
469
  # Get the query options for this Cursor.
313
470
  #
314
471
  # @return [Hash]
315
472
  def query_options_hash
316
- { :selector => @selector,
473
+ BSON::OrderedHash[
474
+ :selector => @selector,
317
475
  :fields => @fields,
318
476
  :skip => @skip,
319
477
  :limit => @limit,
320
478
  :order => @order,
321
479
  :hint => @hint,
322
480
  :snapshot => @snapshot,
323
- :timeout => @timeout }
481
+ :timeout => @timeout,
482
+ :max_scan => @max_scan,
483
+ :return_key => @return_key,
484
+ :show_disk_loc => @show_disk_loc,
485
+ :comment => @comment ]
324
486
  end
325
487
 
326
488
  # Clean output for inspect.
327
489
  def inspect
328
- "<Mongo::Cursor:0x#{object_id.to_s(16)} namespace='#{@db.name}.#{@collection.name}' " +
329
- "@selector=#{@selector.inspect}>"
490
+ "<Mongo::Cursor:0x#{object_id.to_s(16)} namespace='#{full_collection_name}' " +
491
+ "@selector=#{@selector.inspect} @cursor_id=#{@cursor_id}>"
330
492
  end
331
493
 
332
494
  private
@@ -340,7 +502,10 @@ module Mongo
340
502
  {fields => 1}
341
503
  when Array
342
504
  return nil if fields.length.zero?
343
- fields.each_with_object({}) { |field, hash| hash[field] = 1 }
505
+ fields.inject({}) do |hash, field|
506
+ field.is_a?(Hash) ? hash.merge!(field) : hash[field] = 1
507
+ hash
508
+ end
344
509
  when Hash
345
510
  return fields
346
511
  end
@@ -348,16 +513,77 @@ module Mongo
348
513
 
349
514
  # Return the number of documents remaining for this cursor.
350
515
  def num_remaining
351
- refresh if @cache.length == 0
516
+ if @cache.length == 0
517
+ if @query_run && exhaust?
518
+ close
519
+ return 0
520
+ else
521
+ refresh
522
+ end
523
+ end
524
+
352
525
  @cache.length
353
526
  end
354
527
 
528
+ # Refresh the documents in @cache. This means either
529
+ # sending the initial query or sending a GET_MORE operation.
355
530
  def refresh
356
- return if send_initial_query || @cursor_id.zero?
531
+ if !@query_run
532
+ send_initial_query
533
+ elsif !@cursor_id.zero?
534
+ send_get_more
535
+ end
536
+ end
537
+
538
+ # Sends initial query -- which is always a read unless it is a command
539
+ #
540
+ # Upon ConnectionFailure, tries query 3 times if socket was not provided
541
+ # and the query is either not a command or is a secondary_ok command.
542
+ #
543
+ # Pins pools upon successful read and unpins pool upon ConnectionFailure
544
+ #
545
+ def send_initial_query
546
+ tries = 0
547
+ instrument(:find, instrument_payload) do
548
+ begin
549
+ message = construct_query_message
550
+ socket = @socket || checkout_socket_from_connection
551
+ results, @n_received, @cursor_id = @connection.receive_message(
552
+ Mongo::Constants::OP_QUERY, message, nil, socket, @command,
553
+ nil, exhaust?, compile_regex?)
554
+ rescue ConnectionFailure => ex
555
+ socket.close if socket
556
+ @pool = nil
557
+ @connection.unpin_pool
558
+ @connection.refresh
559
+ if tries < 3 && !@socket && (!@command || Mongo::ReadPreference::secondary_ok?(@selector))
560
+ tries += 1
561
+ retry
562
+ else
563
+ raise ex
564
+ end
565
+ rescue OperationFailure, OperationTimeout => ex
566
+ raise ex
567
+ ensure
568
+ socket.checkin unless @socket || socket.nil?
569
+ end
570
+
571
+ if pin_pool?(results.first)
572
+ @connection.pin_pool(socket.pool, read_preference)
573
+ end
574
+
575
+ @returned += @n_received
576
+ @cache += results
577
+ @query_run = true
578
+ close_cursor_if_query_complete
579
+ end
580
+ end
581
+
582
+ def send_get_more
357
583
  message = BSON::ByteBuffer.new([0, 0, 0, 0])
358
584
 
359
585
  # DB name.
360
- BSON::BSON_RUBY.serialize_cstr(message, "#{@db.name}.#{@collection.name}")
586
+ BSON::BSON_RUBY.serialize_cstr(message, full_collection_name)
361
587
 
362
588
  # Number of results to return.
363
589
  if @limit > 0
@@ -372,52 +598,73 @@ module Mongo
372
598
 
373
599
  # Cursor id.
374
600
  message.put_long(@cursor_id)
375
- @logger.debug("MONGODB cursor.refresh() for cursor #{@cursor_id}") if @logger
376
- results, @n_received, @cursor_id = @connection.receive_message(
377
- Mongo::Constants::OP_GET_MORE, message, nil, @socket, @command)
601
+ log(:debug, "cursor.refresh() for cursor #{@cursor_id}") if @logger
602
+
603
+ socket = @pool.checkout
604
+
605
+ begin
606
+ results, @n_received, @cursor_id = @connection.receive_message(
607
+ Mongo::Constants::OP_GET_MORE, message, nil, socket, @command,
608
+ nil, exhaust?, compile_regex?)
609
+ ensure
610
+ socket.checkin
611
+ end
612
+
378
613
  @returned += @n_received
379
614
  @cache += results
380
615
  close_cursor_if_query_complete
381
616
  end
382
617
 
383
- # Run query the first time we request an object from the wire
384
- # TODO: should we be calling instrument_payload even if logging
385
- # is disabled?
386
- def send_initial_query
387
- if @query_run
388
- false
389
- else
390
- message = construct_query_message
391
- @connection.instrument(:find, instrument_payload) do
392
- results, @n_received, @cursor_id = @connection.receive_message(
393
- Mongo::Constants::OP_QUERY, message, nil, @socket, @command)
394
- @returned += @n_received
395
- @cache += results
396
- @query_run = true
397
- close_cursor_if_query_complete
618
+ def checkout_socket_from_connection
619
+ begin
620
+ if @pool
621
+ socket = @pool.checkout
622
+ elsif @command && !Mongo::ReadPreference::secondary_ok?(@selector)
623
+ socket = @connection.checkout_reader({:mode => :primary})
624
+ else
625
+ socket = @connection.checkout_reader(read_preference)
398
626
  end
399
- true
627
+ rescue SystemStackError, NoMemoryError, SystemCallError => ex
628
+ @connection.close
629
+ raise ex
400
630
  end
631
+ @pool = socket.pool
632
+ socket
633
+ end
634
+
635
+ def checkin_socket(sock)
636
+ @connection.checkin(sock)
401
637
  end
402
638
 
403
639
  def construct_query_message
404
- message = BSON::ByteBuffer.new
405
- message.put_int(query_opts)
406
- BSON::BSON_RUBY.serialize_cstr(message, "#{@db.name}.#{@collection.name}")
640
+ message = BSON::ByteBuffer.new("", @connection.max_bson_size + MongoClient::COMMAND_HEADROOM)
641
+ message.put_int(@options)
642
+ BSON::BSON_RUBY.serialize_cstr(message, full_collection_name)
407
643
  message.put_int(@skip)
408
- message.put_int(@limit)
409
- spec = query_contains_special_fields? ? construct_query_spec : @selector
410
- message.put_binary(BSON::BSON_CODER.serialize(spec, false).to_s)
411
- message.put_binary(BSON::BSON_CODER.serialize(@fields, false).to_s) if @fields
644
+ @batch_size > 1 ? message.put_int(@batch_size) : message.put_int(@limit)
645
+ if query_contains_special_fields? && @bson # costs two serialize calls
646
+ query_message = BSON::BSON_CODER.serialize(@selector, false, false, @connection.max_bson_size + MongoClient::APPEND_HEADROOM)
647
+ query_message.grow(@bson)
648
+ query_spec = construct_query_spec
649
+ query_spec.delete('$query')
650
+ query_message.grow(BSON::BSON_CODER.serialize(query_spec, false, false, @connection.max_bson_size))
651
+ else # costs only one serialize call
652
+ spec = query_contains_special_fields? ? construct_query_spec : @selector
653
+ spec.merge!(@opts)
654
+ query_message = BSON::BSON_CODER.serialize(spec, false, false, @connection.max_bson_size + MongoClient::APPEND_HEADROOM)
655
+ query_message.grow(@bson) if @bson
656
+ end
657
+ message.put_binary(query_message.to_s)
658
+ message.put_binary(BSON::BSON_CODER.serialize(@fields, false, false, @connection.max_bson_size).to_s) if @fields
412
659
  message
413
660
  end
414
661
 
415
662
  def instrument_payload
416
663
  log = { :database => @db.name, :collection => @collection.name, :selector => selector }
417
- log[:fields] = @fields if @fields
418
- log[:skip] = @skip if @skip && (@skip > 0)
419
- log[:limit] = @limit if @limit && (@limit > 0)
420
- log[:order] = @order if @order
664
+ log[:fields] = @fields if @fields
665
+ log[:skip] = @skip if @skip && (@skip != 0)
666
+ log[:limit] = @limit if @limit && (@limit != 0)
667
+ log[:order] = @order if @order
421
668
  log
422
669
  end
423
670
 
@@ -429,16 +676,25 @@ module Mongo
429
676
  spec['$hint'] = @hint if @hint && @hint.length > 0
430
677
  spec['$explain'] = true if @explain
431
678
  spec['$snapshot'] = true if @snapshot
679
+ spec['$maxScan'] = @max_scan if @max_scan
680
+ spec['$returnKey'] = true if @return_key
681
+ spec['$showDiskLoc'] = true if @show_disk_loc
682
+ spec['$comment'] = @comment if @comment
683
+ spec['$maxTimeMS'] = @max_time_ms if @max_time_ms
684
+ if needs_read_pref?
685
+ read_pref = Mongo::ReadPreference::mongos(@read, @tag_sets)
686
+ spec['$readPreference'] = read_pref if read_pref
687
+ end
432
688
  spec
433
689
  end
434
690
 
435
- # Returns true if the query contains order, explain, hint, or snapshot.
436
- def query_contains_special_fields?
437
- @order || @explain || @hint || @snapshot
691
+ def needs_read_pref?
692
+ @connection.mongos? && @read != :primary
438
693
  end
439
694
 
440
- def to_s
441
- "DBResponse(flags=#@result_flags, cursor_id=#@cursor_id, start=#@starting_from)"
695
+ def query_contains_special_fields?
696
+ @order || @explain || @hint || @snapshot || @show_disk_loc ||
697
+ @max_scan || @return_key || @comment || @max_time_ms || needs_read_pref?
442
698
  end
443
699
 
444
700
  def close_cursor_if_query_complete
@@ -447,10 +703,40 @@ module Mongo
447
703
  end
448
704
  end
449
705
 
706
+ # Check whether the exhaust option is set
707
+ #
708
+ # @return [true, false] The state of the exhaust flag.
709
+ def exhaust?(opts = options)
710
+ !(opts & OP_QUERY_EXHAUST).zero?
711
+ end
712
+
450
713
  def check_modifiable
451
714
  if @query_run || @closed
452
715
  raise InvalidOperation, "Cannot modify the query once it has been run or closed."
453
716
  end
454
717
  end
718
+
719
+ def check_command_cursor
720
+ if @command_cursor
721
+ raise InvalidOperation, "Cannot call #{caller.first} on command cursors"
722
+ end
723
+ end
724
+
725
+ def compile_regex?
726
+ @compile_regex
727
+ end
728
+
729
+ def generate_index_name(spec)
730
+ indexes = []
731
+ spec.each_pair do |field, type|
732
+ indexes.push("#{field}_#{type}")
733
+ end
734
+ indexes.join("_")
735
+ end
736
+
737
+ def pin_pool?(response)
738
+ ( response && (response['cursor'] || response['cursors']) ) ||
739
+ ( !@socket && !@command )
740
+ end
455
741
  end
456
742
  end