jonbell-mongo 1.3.1.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (88) hide show
  1. data/LICENSE.txt +190 -0
  2. data/README.md +333 -0
  3. data/Rakefile +215 -0
  4. data/bin/mongo_console +21 -0
  5. data/docs/CREDITS.md +123 -0
  6. data/docs/FAQ.md +116 -0
  7. data/docs/GridFS.md +158 -0
  8. data/docs/HISTORY.md +263 -0
  9. data/docs/RELEASES.md +33 -0
  10. data/docs/REPLICA_SETS.md +72 -0
  11. data/docs/TUTORIAL.md +247 -0
  12. data/docs/WRITE_CONCERN.md +28 -0
  13. data/lib/mongo.rb +97 -0
  14. data/lib/mongo/collection.rb +895 -0
  15. data/lib/mongo/connection.rb +926 -0
  16. data/lib/mongo/cursor.rb +474 -0
  17. data/lib/mongo/db.rb +617 -0
  18. data/lib/mongo/exceptions.rb +71 -0
  19. data/lib/mongo/gridfs/grid.rb +107 -0
  20. data/lib/mongo/gridfs/grid_ext.rb +57 -0
  21. data/lib/mongo/gridfs/grid_file_system.rb +146 -0
  22. data/lib/mongo/gridfs/grid_io.rb +485 -0
  23. data/lib/mongo/gridfs/grid_io_fix.rb +38 -0
  24. data/lib/mongo/repl_set_connection.rb +356 -0
  25. data/lib/mongo/util/conversions.rb +89 -0
  26. data/lib/mongo/util/core_ext.rb +60 -0
  27. data/lib/mongo/util/pool.rb +177 -0
  28. data/lib/mongo/util/server_version.rb +71 -0
  29. data/lib/mongo/util/support.rb +82 -0
  30. data/lib/mongo/util/uri_parser.rb +185 -0
  31. data/mongo.gemspec +34 -0
  32. data/test/auxillary/1.4_features.rb +166 -0
  33. data/test/auxillary/authentication_test.rb +68 -0
  34. data/test/auxillary/autoreconnect_test.rb +41 -0
  35. data/test/auxillary/fork_test.rb +30 -0
  36. data/test/auxillary/repl_set_auth_test.rb +58 -0
  37. data/test/auxillary/slave_connection_test.rb +36 -0
  38. data/test/auxillary/threaded_authentication_test.rb +101 -0
  39. data/test/bson/binary_test.rb +15 -0
  40. data/test/bson/bson_test.rb +654 -0
  41. data/test/bson/byte_buffer_test.rb +208 -0
  42. data/test/bson/hash_with_indifferent_access_test.rb +38 -0
  43. data/test/bson/json_test.rb +17 -0
  44. data/test/bson/object_id_test.rb +154 -0
  45. data/test/bson/ordered_hash_test.rb +210 -0
  46. data/test/bson/timestamp_test.rb +24 -0
  47. data/test/collection_test.rb +910 -0
  48. data/test/connection_test.rb +324 -0
  49. data/test/conversions_test.rb +119 -0
  50. data/test/cursor_fail_test.rb +75 -0
  51. data/test/cursor_message_test.rb +43 -0
  52. data/test/cursor_test.rb +483 -0
  53. data/test/db_api_test.rb +738 -0
  54. data/test/db_connection_test.rb +15 -0
  55. data/test/db_test.rb +315 -0
  56. data/test/grid_file_system_test.rb +259 -0
  57. data/test/grid_io_test.rb +209 -0
  58. data/test/grid_test.rb +258 -0
  59. data/test/load/thin/load.rb +24 -0
  60. data/test/load/unicorn/load.rb +23 -0
  61. data/test/replica_sets/connect_test.rb +112 -0
  62. data/test/replica_sets/connection_string_test.rb +32 -0
  63. data/test/replica_sets/count_test.rb +35 -0
  64. data/test/replica_sets/insert_test.rb +53 -0
  65. data/test/replica_sets/pooled_insert_test.rb +55 -0
  66. data/test/replica_sets/query_secondaries.rb +108 -0
  67. data/test/replica_sets/query_test.rb +51 -0
  68. data/test/replica_sets/replication_ack_test.rb +66 -0
  69. data/test/replica_sets/rs_test_helper.rb +27 -0
  70. data/test/safe_test.rb +68 -0
  71. data/test/support/hash_with_indifferent_access.rb +186 -0
  72. data/test/support/keys.rb +45 -0
  73. data/test/support_test.rb +18 -0
  74. data/test/test_helper.rb +102 -0
  75. data/test/threading/threading_with_large_pool_test.rb +90 -0
  76. data/test/threading_test.rb +87 -0
  77. data/test/tools/auth_repl_set_manager.rb +14 -0
  78. data/test/tools/repl_set_manager.rb +266 -0
  79. data/test/unit/collection_test.rb +130 -0
  80. data/test/unit/connection_test.rb +85 -0
  81. data/test/unit/cursor_test.rb +109 -0
  82. data/test/unit/db_test.rb +94 -0
  83. data/test/unit/grid_test.rb +49 -0
  84. data/test/unit/pool_test.rb +9 -0
  85. data/test/unit/repl_set_connection_test.rb +59 -0
  86. data/test/unit/safe_test.rb +125 -0
  87. data/test/uri_test.rb +91 -0
  88. metadata +224 -0
@@ -0,0 +1,474 @@
1
+ # encoding: UTF-8
2
+
3
+ # Copyright (C) 2008-2011 10gen Inc.
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+
17
+ module Mongo
18
+
19
+ # A cursor over query results. Returned objects are hashes.
20
+ class Cursor
21
+ include Mongo::Conversions
22
+ include Enumerable
23
+
24
+ attr_reader :collection, :selector, :fields,
25
+ :order, :hint, :snapshot, :timeout,
26
+ :full_collection_name, :transformer
27
+
28
+ # Create a new cursor.
29
+ #
30
+ # Note: cursors are created when executing queries using [Collection#find] and other
31
+ # similar methods. Application developers shouldn't have to create cursors manually.
32
+ #
33
+ # @return [Cursor]
34
+ #
35
+ # @core cursors constructor_details
36
+ def initialize(collection, opts={})
37
+ @cursor_id = nil
38
+
39
+ @db = collection.db
40
+ @collection = collection
41
+ @connection = @db.connection
42
+ @logger = @connection.logger
43
+
44
+ # Query selector
45
+ @selector = opts[:selector] || {}
46
+
47
+ # Special operators that form part of $query
48
+ @order = opts[:order]
49
+ @explain = opts[:explain]
50
+ @hint = opts[:hint]
51
+ @snapshot = opts[:snapshot]
52
+ @max_scan = opts.fetch(:max_scan, nil)
53
+ @return_key = opts.fetch(:return_key, nil)
54
+ @show_disk_loc = opts.fetch(:show_disk_loc, nil)
55
+
56
+ # Wire-protocol settings
57
+ @fields = convert_fields_for_query(opts[:fields])
58
+ @skip = opts[:skip] || 0
59
+ @limit = opts[:limit] || 0
60
+ @tailable = opts[:tailable] || false
61
+ @timeout = opts.fetch(:timeout, true)
62
+
63
+ # Use this socket for the query
64
+ @socket = opts[:socket]
65
+
66
+ @closed = false
67
+ @query_run = false
68
+
69
+ @transformer = opts[:transformer]
70
+ batch_size(opts[:batch_size] || 0)
71
+
72
+ @full_collection_name = "#{@collection.db.name}.#{@collection.name}"
73
+ @cache = []
74
+ @returned = 0
75
+
76
+ if @collection.name =~ /^\$cmd/ || @collection.name =~ /^system/
77
+ @command = true
78
+ else
79
+ @command = false
80
+ end
81
+ end
82
+
83
+ # Get the next document specified the cursor options.
84
+ #
85
+ # @return [Hash, Nil] the next document or Nil if no documents remain.
86
+ def next_document
87
+ refresh if @cache.length == 0
88
+ doc = @cache.shift
89
+
90
+ if doc && doc['$err']
91
+ err = doc['$err']
92
+
93
+ # If the server has stopped being the master (e.g., it's one of a
94
+ # pair but it has died or something like that) then we close that
95
+ # connection. The next request will re-open on master server.
96
+ if err == "not master"
97
+ @connection.close
98
+ raise ConnectionFailure, err
99
+ end
100
+
101
+ raise OperationFailure, err
102
+ end
103
+
104
+ if @transformer.nil?
105
+ doc
106
+ else
107
+ @transformer.call(doc) if doc
108
+ end
109
+ end
110
+ alias :next :next_document
111
+
112
+ # Reset this cursor on the server. Cursor options, such as the
113
+ # query string and the values for skip and limit, are preserved.
114
+ def rewind!
115
+ close
116
+ @cache.clear
117
+ @cursor_id = nil
118
+ @closed = false
119
+ @query_run = false
120
+ @n_received = nil
121
+ true
122
+ end
123
+
124
+ # Determine whether this cursor has any remaining results.
125
+ #
126
+ # @return [Boolean]
127
+ def has_next?
128
+ num_remaining > 0
129
+ end
130
+
131
+ # Get the size of the result set for this query.
132
+ #
133
+ # @param [Boolean] whether of not to take notice of skip and limit
134
+ #
135
+ # @return [Integer] the number of objects in the result set for this query.
136
+ #
137
+ # @raise [OperationFailure] on a database error.
138
+ def count(skip_and_limit = false)
139
+ command = BSON::OrderedHash["count", @collection.name, "query", @selector]
140
+
141
+ if skip_and_limit
142
+ command.merge!(BSON::OrderedHash["limit", @limit]) if @limit != 0
143
+ command.merge!(BSON::OrderedHash["skip", @skip]) if @skip != 0
144
+ end
145
+
146
+ command.merge!(BSON::OrderedHash["fields", @fields])
147
+
148
+ response = @db.command(command)
149
+ return response['n'].to_i if Mongo::Support.ok?(response)
150
+ return 0 if response['errmsg'] == "ns missing"
151
+ raise OperationFailure, "Count failed: #{response['errmsg']}"
152
+ end
153
+
154
+ # Sort this cursor's results.
155
+ #
156
+ # This method overrides any sort order specified in the Collection#find
157
+ # method, and only the last sort applied has an effect.
158
+ #
159
+ # @param [Symbol, Array] key_or_list either 1) a key to sort by or 2)
160
+ # an array of [key, direction] pairs to sort by. Direction should
161
+ # be specified as Mongo::ASCENDING (or :ascending / :asc) or Mongo::DESCENDING (or :descending / :desc)
162
+ #
163
+ # @raise [InvalidOperation] if this cursor has already been used.
164
+ #
165
+ # @raise [InvalidSortValueError] if the specified order is invalid.
166
+ def sort(key_or_list, direction=nil)
167
+ check_modifiable
168
+
169
+ if !direction.nil?
170
+ order = [[key_or_list, direction]]
171
+ else
172
+ order = key_or_list
173
+ end
174
+
175
+ @order = order
176
+ self
177
+ end
178
+
179
+ # Limit the number of results to be returned by this cursor.
180
+ #
181
+ # This method overrides any limit specified in the Collection#find method,
182
+ # and only the last limit applied has an effect.
183
+ #
184
+ # @return [Integer] the current number_to_return if no parameter is given.
185
+ #
186
+ # @raise [InvalidOperation] if this cursor has already been used.
187
+ #
188
+ # @core limit limit-instance_method
189
+ def limit(number_to_return=nil)
190
+ return @limit unless number_to_return
191
+ check_modifiable
192
+
193
+ @limit = number_to_return
194
+ self
195
+ end
196
+
197
+ # Skips the first +number_to_skip+ results of this cursor.
198
+ # Returns the current number_to_skip if no parameter is given.
199
+ #
200
+ # This method overrides any skip specified in the Collection#find method,
201
+ # and only the last skip applied has an effect.
202
+ #
203
+ # @return [Integer]
204
+ #
205
+ # @raise [InvalidOperation] if this cursor has already been used.
206
+ def skip(number_to_skip=nil)
207
+ return @skip unless number_to_skip
208
+ check_modifiable
209
+
210
+ @skip = number_to_skip
211
+ self
212
+ end
213
+
214
+ # Set the batch size for server responses.
215
+ #
216
+ # Note that the batch size will take effect only on queries
217
+ # where the number to be returned is greater than 100.
218
+ #
219
+ # @param [Integer] size either 0 or some integer greater than 1. If 0,
220
+ # the server will determine the batch size.
221
+ #
222
+ # @return [Cursor]
223
+ def batch_size(size=0)
224
+ check_modifiable
225
+ if size < 0 || size == 1
226
+ raise ArgumentError, "Invalid value for batch_size #{size}; must be 0 or > 1."
227
+ else
228
+ @batch_size = size > @limit ? @limit : size
229
+ end
230
+
231
+ self
232
+ end
233
+
234
+ # Iterate over each document in this cursor, yielding it to the given
235
+ # block.
236
+ #
237
+ # Iterating over an entire cursor will close it.
238
+ #
239
+ # @yield passes each document to a block for processing.
240
+ #
241
+ # @example if 'comments' represents a collection of comments:
242
+ # comments.find.each do |doc|
243
+ # puts doc['user']
244
+ # end
245
+ def each
246
+ #num_returned = 0
247
+ #while has_next? && (@limit <= 0 || num_returned < @limit)
248
+ while doc = next_document
249
+ yield doc #next_document
250
+ #num_returned += 1
251
+ end
252
+ end
253
+
254
+ # Receive all the documents from this cursor as an array of hashes.
255
+ #
256
+ # Notes:
257
+ #
258
+ # If you've already started iterating over the cursor, the array returned
259
+ # by this method contains only the remaining documents. See Cursor#rewind! if you
260
+ # need to reset the cursor.
261
+ #
262
+ # Use of this method is discouraged - in most cases, it's much more
263
+ # efficient to retrieve documents as you need them by iterating over the cursor.
264
+ #
265
+ # @return [Array] an array of documents.
266
+ def to_a
267
+ super
268
+ end
269
+
270
+ # Get the explain plan for this cursor.
271
+ #
272
+ # @return [Hash] a document containing the explain plan for this cursor.
273
+ #
274
+ # @core explain explain-instance_method
275
+ def explain
276
+ c = Cursor.new(@collection, query_options_hash.merge(:limit => -@limit.abs, :explain => true))
277
+ explanation = c.next_document
278
+ c.close
279
+
280
+ explanation
281
+ end
282
+
283
+ # Close the cursor.
284
+ #
285
+ # Note: if a cursor is read until exhausted (read until Mongo::Constants::OP_QUERY or
286
+ # Mongo::Constants::OP_GETMORE returns zero for the cursor id), there is no need to
287
+ # close it manually.
288
+ #
289
+ # Note also: Collection#find takes an optional block argument which can be used to
290
+ # ensure that your cursors get closed.
291
+ #
292
+ # @return [True]
293
+ def close
294
+ if @cursor_id && @cursor_id != 0
295
+ message = BSON::ByteBuffer.new([0, 0, 0, 0])
296
+ message.put_int(1)
297
+ message.put_long(@cursor_id)
298
+ @logger.debug("MONGODB cursor.close #{@cursor_id}") if @logger
299
+ @connection.send_message(Mongo::Constants::OP_KILL_CURSORS, message, :connection => :reader)
300
+ end
301
+ @cursor_id = 0
302
+ @closed = true
303
+ end
304
+
305
+ # Is this cursor closed?
306
+ #
307
+ # @return [Boolean]
308
+ def closed?; @closed; end
309
+
310
+ # Returns an integer indicating which query options have been selected.
311
+ #
312
+ # @return [Integer]
313
+ #
314
+ # @see http://www.mongodb.org/display/DOCS/Mongo+Wire+Protocol#MongoWireProtocol-Mongo::Constants::OPQUERY
315
+ # The MongoDB wire protocol.
316
+ def query_opts
317
+ opts = 0
318
+ opts |= Mongo::Constants::OP_QUERY_NO_CURSOR_TIMEOUT unless @timeout
319
+ opts |= Mongo::Constants::OP_QUERY_SLAVE_OK if @connection.slave_ok?
320
+ opts |= Mongo::Constants::OP_QUERY_TAILABLE if @tailable
321
+ opts
322
+ end
323
+
324
+ # Get the query options for this Cursor.
325
+ #
326
+ # @return [Hash]
327
+ def query_options_hash
328
+ { :selector => @selector,
329
+ :fields => @fields,
330
+ :skip => @skip,
331
+ :limit => @limit,
332
+ :order => @order,
333
+ :hint => @hint,
334
+ :snapshot => @snapshot,
335
+ :timeout => @timeout,
336
+ :max_scan => @max_scan,
337
+ :return_key => @return_key,
338
+ :show_disk_loc => @show_disk_loc }
339
+ end
340
+
341
+ # Clean output for inspect.
342
+ def inspect
343
+ "<Mongo::Cursor:0x#{object_id.to_s(16)} namespace='#{@db.name}.#{@collection.name}' " +
344
+ "@selector=#{@selector.inspect}>"
345
+ end
346
+
347
+ private
348
+
349
+ # Convert the +:fields+ parameter from a single field name or an array
350
+ # of fields names to a hash, with the field names for keys and '1' for each
351
+ # value.
352
+ def convert_fields_for_query(fields)
353
+ case fields
354
+ when String, Symbol
355
+ {fields => 1}
356
+ when Array
357
+ return nil if fields.length.zero?
358
+ fields.each_with_object({}) { |field, hash| hash[field] = 1 }
359
+ when Hash
360
+ return fields
361
+ end
362
+ end
363
+
364
+ # Return the number of documents remaining for this cursor.
365
+ def num_remaining
366
+ refresh if @cache.length == 0
367
+ @cache.length
368
+ end
369
+
370
+ def refresh
371
+ return if send_initial_query || @cursor_id.zero?
372
+ message = BSON::ByteBuffer.new([0, 0, 0, 0])
373
+
374
+ # DB name.
375
+ BSON::BSON_RUBY.serialize_cstr(message, "#{@db.name}.#{@collection.name}")
376
+
377
+ # Number of results to return.
378
+ if @limit > 0
379
+ limit = @limit - @returned
380
+ if @batch_size > 0
381
+ limit = limit < @batch_size ? limit : @batch_size
382
+ end
383
+ message.put_int(limit)
384
+ else
385
+ message.put_int(@batch_size)
386
+ end
387
+
388
+ # Cursor id.
389
+ message.put_long(@cursor_id)
390
+ @logger.debug("MONGODB cursor.refresh() for cursor #{@cursor_id}") if @logger
391
+ results, @n_received, @cursor_id = @connection.receive_message(
392
+ Mongo::Constants::OP_GET_MORE, message, nil, @socket, @command)
393
+ @returned += @n_received
394
+ @cache += results
395
+ close_cursor_if_query_complete
396
+ end
397
+
398
+ # Run query the first time we request an object from the wire
399
+ # TODO: should we be calling instrument_payload even if logging
400
+ # is disabled?
401
+ def send_initial_query
402
+ if @query_run
403
+ false
404
+ else
405
+ message = construct_query_message
406
+ @connection.instrument(:find, instrument_payload) do
407
+ results, @n_received, @cursor_id = @connection.receive_message(
408
+ Mongo::Constants::OP_QUERY, message, nil, @socket, @command)
409
+ @returned += @n_received
410
+ @cache += results
411
+ @query_run = true
412
+ close_cursor_if_query_complete
413
+ end
414
+ true
415
+ end
416
+ end
417
+
418
+ def construct_query_message
419
+ message = BSON::ByteBuffer.new
420
+ message.put_int(query_opts)
421
+ BSON::BSON_RUBY.serialize_cstr(message, "#{@db.name}.#{@collection.name}")
422
+ message.put_int(@skip)
423
+ message.put_int(@limit)
424
+ spec = query_contains_special_fields? ? construct_query_spec : @selector
425
+ message.put_binary(BSON::BSON_CODER.serialize(spec, false).to_s)
426
+ message.put_binary(BSON::BSON_CODER.serialize(@fields, false).to_s) if @fields
427
+ message
428
+ end
429
+
430
+ def instrument_payload
431
+ log = { :database => @db.name, :collection => @collection.name, :selector => selector }
432
+ log[:fields] = @fields if @fields
433
+ log[:skip] = @skip if @skip && (@skip > 0)
434
+ log[:limit] = @limit if @limit && (@limit > 0)
435
+ log[:order] = @order if @order
436
+ log
437
+ end
438
+
439
+ def construct_query_spec
440
+ return @selector if @selector.has_key?('$query')
441
+ spec = BSON::OrderedHash.new
442
+ spec['$query'] = @selector
443
+ spec['$orderby'] = Mongo::Support.format_order_clause(@order) if @order
444
+ spec['$hint'] = @hint if @hint && @hint.length > 0
445
+ spec['$explain'] = true if @explain
446
+ spec['$snapshot'] = true if @snapshot
447
+ spec['$maxscan'] = @max_scan if @max_scan
448
+ spec['$returnKey'] = true if @return_key
449
+ spec['$showDiskLoc'] = true if @show_disk_loc
450
+ spec
451
+ end
452
+
453
+ # Returns true if the query contains order, explain, hint, or snapshot.
454
+ def query_contains_special_fields?
455
+ @order || @explain || @hint || @snapshot
456
+ end
457
+
458
+ def to_s
459
+ "DBResponse(flags=#@result_flags, cursor_id=#@cursor_id, start=#@starting_from)"
460
+ end
461
+
462
+ def close_cursor_if_query_complete
463
+ if @limit > 0 && @returned >= @limit
464
+ close
465
+ end
466
+ end
467
+
468
+ def check_modifiable
469
+ if @query_run || @closed
470
+ raise InvalidOperation, "Cannot modify the query once it has been run or closed."
471
+ end
472
+ end
473
+ end
474
+ end