mongo 1.3.0 → 1.3.1

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -18,17 +18,18 @@ for much more:
18
18
 
19
19
  require 'rubygems'
20
20
  require 'mongo'
21
- include Mongo
22
21
 
23
- db = Connection.new.db('sample-db')
24
- coll = db.collection('test')
22
+ @conn = Mongo::Connection.new
23
+ @db = @conn['sample-db']
24
+ @coll = @db['test']
25
25
 
26
- coll.remove
26
+ @coll.remove
27
27
  3.times do |i|
28
- coll.insert({'a' => i+1})
28
+ @coll.insert({'a' => i+1})
29
29
  end
30
- puts "There are #{coll.count()} records. Here they are:"
31
- coll.find().each { |doc| puts doc.inspect }
30
+
31
+ puts "There are #{@coll.count} records. Here they are:"
32
+ @coll.find.each { |doc| puts doc.inspect }
32
33
 
33
34
  # Installation
34
35
 
@@ -1,5 +1,24 @@
1
1
  # MongoDB Ruby Driver History
2
2
 
3
+ ### 1.3.1
4
+ 2011-5-10
5
+
6
+ * Fix GridIO#gets infinite loop error (Ryan McGeary)
7
+ * Fix BSON::OrderedHash#reject! leaving keys with null values (rpt. by Ben Poweski)
8
+ * Minor semantic fix for OrderedHash#reject!
9
+ * Fix Mongo::DB to allow symbols in method traversing collection names (rpt. by Chris Griego)
10
+ * Support new server regex option "s" (dotall). This is folded in with \m in Ruby.
11
+ * Fix so that Cursor#close hits the right node when :read_secondary is enabled.
12
+ * Support maxScan, showDiskLoc, and returnKey cursor options.
13
+ * Make DB#validate_collection compatible with server v1.9.1.
14
+ * Fix so that GridIO#gets returns local md5 with md5 matches server md5 (Steve Tantra).
15
+ * Fix bug in BSON::OrderedHash that prevents YAML.load (Ian Warshak).
16
+ * Fix example from /examples.
17
+ * Ensure that we do not modify hash arguments by calling Hash#dup when appropriate.
18
+ * Ensure that JRuby deserializer preserves binary subtypes properly.
19
+ * Fix for streaming an empty file into GridFS (Daniël van de Burgt).
20
+ * Minor doc fixes.
21
+
3
22
  ### 1.3.0
4
23
  2011-4-04
5
24
 
@@ -19,7 +19,7 @@
19
19
  $:.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
20
20
 
21
21
  module Mongo
22
- VERSION = "1.3.0"
22
+ VERSION = "1.3.1"
23
23
  end
24
24
 
25
25
  module Mongo
@@ -91,7 +91,7 @@ module Mongo
91
91
  # Return a sub-collection of this collection by name. If 'users' is a collection, then
92
92
  # 'users.comments' is a sub-collection of users.
93
93
  #
94
- # @param [String] name
94
+ # @param [String, Symbol] name
95
95
  # the collection to return
96
96
  #
97
97
  # @raise [Mongo::InvalidNSName]
@@ -101,7 +101,8 @@ module Mongo
101
101
  # the specified sub-collection
102
102
  def [](name)
103
103
  name = "#{self.name}.#{name}"
104
- return Collection.new(name, db) if !db.strict? || db.collection_names.include?(name)
104
+ return Collection.new(name, db) if !db.strict? ||
105
+ db.collection_names.include?(name.to_s)
105
106
  raise "Collection #{name} doesn't exist. Currently in strict mode."
106
107
  end
107
108
 
@@ -147,16 +148,23 @@ module Mongo
147
148
  # @option opts [Integer] :limit maximum number of documents to return
148
149
  # @option opts [Array] :sort an array of [key, direction] pairs to sort by. Direction should
149
150
  # be specified as Mongo::ASCENDING (or :ascending / :asc) or Mongo::DESCENDING (or :descending / :desc)
150
- # @option opts [String, Array, OrderedHash] :hint hint for query optimizer, usually not necessary if using MongoDB > 1.1
151
+ # @option opts [String, Array, OrderedHash] :hint hint for query optimizer, usually not necessary if
152
+ # using MongoDB > 1.1
151
153
  # @option opts [Boolean] :snapshot (false) if true, snapshot mode will be used for this query.
152
154
  # Snapshot mode assures no duplicates are returned, or objects missed, which were preset at both the start and
153
- # end of the query's execution. For details see http://www.mongodb.org/display/DOCS/How+to+do+Snapshotting+in+the+Mongo+Database
154
- # @option opts [Boolean] :batch_size (100) the number of documents to returned by the database per GETMORE operation. A value of 0
155
- # will let the database server decide how many results to returns. This option can be ignored for most use cases.
155
+ # end of the query's execution.
156
+ # For details see http://www.mongodb.org/display/DOCS/How+to+do+Snapshotting+in+the+Mongo+Database
157
+ # @option opts [Boolean] :batch_size (100) the number of documents to returned by the database per
158
+ # GETMORE operation. A value of 0 will let the database server decide how many results to returns.
159
+ # This option can be ignored for most use cases.
156
160
  # @option opts [Boolean] :timeout (true) when +true+, the returned cursor will be subject to
157
- # the normal cursor timeout behavior of the mongod process. When +false+, the returned cursor will never timeout. Note
158
- # that disabling timeout will only work when #find is invoked with a block. This is to prevent any inadvertant failure to
159
- # close the cursor, as the cursor is explicitly closed when block code finishes.
161
+ # the normal cursor timeout behavior of the mongod process. When +false+, the returned cursor will
162
+ # never timeout. Note that disabling timeout will only work when #find is invoked with a block.
163
+ # This is to prevent any inadvertant failure to close the cursor, as the cursor is explicitly
164
+ # closed when block code finishes.
165
+ # @option opts [Integer] :max_scan (nil) Limit the number of items to scan on both collection scans and indexed queries..
166
+ # @option opts [Boolean] :show_disk_loc (false) Return the disk location of each query result (for debugging).
167
+ # @option opts [Boolean] :return_key (false) Return the index key used to obtain the result (for debugging).
160
168
  # @option opts [Block] :transformer (nil) a block for tranforming returned documents.
161
169
  # This is normally used by object mappers to convert each returned document to an instance of a class.
162
170
  #
@@ -168,6 +176,7 @@ module Mongo
168
176
  #
169
177
  # @core find find-instance_method
170
178
  def find(selector={}, opts={})
179
+ opts = opts.dup
171
180
  fields = opts.delete(:fields)
172
181
  fields = ["_id"] if fields && fields.empty?
173
182
  skip = opts.delete(:skip) || skip || 0
@@ -385,7 +394,7 @@ module Mongo
385
394
  if safe
386
395
  @connection.send_message_with_safe_check(Mongo::Constants::OP_UPDATE, message, @db.name, nil, safe)
387
396
  else
388
- @connection.send_message(Mongo::Constants::OP_UPDATE, message, nil)
397
+ @connection.send_message(Mongo::Constants::OP_UPDATE, message)
389
398
  end
390
399
  end
391
400
  end
@@ -433,8 +442,9 @@ module Mongo
433
442
  #
434
443
  # @core indexes create_index-instance_method
435
444
  def create_index(spec, opts={})
436
- opts[:dropDups] = opts.delete(:drop_dups) if opts[:drop_dups]
445
+ opts[:dropDups] = opts[:drop_dups] if opts[:drop_dups]
437
446
  field_spec = parse_index_spec(spec)
447
+ opts = opts.dup
438
448
  name = opts.delete(:name) || generate_index_name(field_spec)
439
449
  name = name.to_s if name
440
450
 
@@ -462,7 +472,7 @@ module Mongo
462
472
  now = Time.now.utc.to_i
463
473
  field_spec = parse_index_spec(spec)
464
474
 
465
- name = opts.delete(:name) || generate_index_name(field_spec)
475
+ name = opts[:name] || generate_index_name(field_spec)
466
476
  name = name.to_s if name
467
477
 
468
478
  if !@cache[name] || @cache[name] <= now
@@ -555,7 +565,7 @@ module Mongo
555
565
  def map_reduce(map, reduce, opts={})
556
566
  map = BSON::Code.new(map) unless map.is_a?(BSON::Code)
557
567
  reduce = BSON::Code.new(reduce) unless reduce.is_a?(BSON::Code)
558
- raw = opts.delete(:raw)
568
+ raw = opts[:raw]
559
569
 
560
570
  hash = BSON::OrderedHash.new
561
571
  hash['mapreduce'] = self.name
@@ -867,7 +877,7 @@ module Mongo
867
877
  if safe
868
878
  @connection.send_message_with_safe_check(Mongo::Constants::OP_INSERT, message, @db.name, nil, safe)
869
879
  else
870
- @connection.send_message(Mongo::Constants::OP_INSERT, message, nil)
880
+ @connection.send_message(Mongo::Constants::OP_INSERT, message)
871
881
  end
872
882
  end
873
883
  documents.collect { |o| o[:_id] || o['_id'] }
@@ -381,15 +381,36 @@ module Mongo
381
381
  # @param [Integer] operation a MongoDB opcode.
382
382
  # @param [BSON::ByteBuffer] message a message to send to the database.
383
383
  #
384
+ # @option opts [Symbol] :connection (:writer) The connection to which
385
+ # this message should be sent. Valid options are :writer and :reader.
386
+ #
384
387
  # @return [Integer] number of bytes sent
385
- def send_message(operation, message, log_message=nil)
388
+ def send_message(operation, message, opts={})
389
+ if opts.is_a?(String)
390
+ warn "Connection#send_message no longer takes a string log message. " +
391
+ "Logging is now handled within the Collection and Cursor classes."
392
+ opts = {}
393
+ end
394
+
395
+ connection = opts.fetch(:connection, :writer)
396
+
386
397
  begin
387
398
  add_message_headers(message, operation)
388
399
  packed_message = message.to_s
389
- socket = checkout_writer
400
+
401
+ if connection == :writer
402
+ socket = checkout_writer
403
+ else
404
+ socket = checkout_reader
405
+ end
406
+
390
407
  send_message_on_socket(packed_message, socket)
391
408
  ensure
392
- checkin_writer(socket)
409
+ if connection == :writer
410
+ checkin_writer(socket)
411
+ else
412
+ checkin_reader(socket)
413
+ end
393
414
  end
394
415
  end
395
416
 
@@ -437,7 +458,10 @@ module Mongo
437
458
  #
438
459
  # @param [Integer] operation a MongoDB opcode.
439
460
  # @param [BSON::ByteBuffer] message a message to send to the database.
461
+ # @param [String] log_message this is currently a no-op and will be removed.
440
462
  # @param [Socket] socket a socket to use in lieu of checking out a new one.
463
+ # @param [Boolean] command (false) indicate whether this is a command. If this is a command,
464
+ # the message will be sent to the primary node.
441
465
  #
442
466
  # @return [Array]
443
467
  # An array whose indexes include [0] documents returned, [1] number of document received,
@@ -41,19 +41,31 @@ module Mongo
41
41
  @connection = @db.connection
42
42
  @logger = @connection.logger
43
43
 
44
+ # Query selector
44
45
  @selector = opts[:selector] || {}
45
- @fields = convert_fields_for_query(opts[:fields])
46
- @skip = opts[:skip] || 0
47
- @limit = opts[:limit] || 0
46
+
47
+ # Special operators that form part of $query
48
48
  @order = opts[:order]
49
+ @explain = opts[:explain]
49
50
  @hint = opts[:hint]
50
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
51
61
  @timeout = opts.fetch(:timeout, true)
52
- @explain = opts[:explain]
62
+
63
+ # Use this socket for the query
53
64
  @socket = opts[:socket]
54
- @tailable = opts[:tailable] || false
65
+
55
66
  @closed = false
56
67
  @query_run = false
68
+
57
69
  @transformer = opts[:transformer]
58
70
  batch_size(opts[:batch_size] || 0)
59
71
 
@@ -284,7 +296,7 @@ module Mongo
284
296
  message.put_int(1)
285
297
  message.put_long(@cursor_id)
286
298
  @logger.debug("MONGODB cursor.close #{@cursor_id}") if @logger
287
- @connection.send_message(Mongo::Constants::OP_KILL_CURSORS, message, nil)
299
+ @connection.send_message(Mongo::Constants::OP_KILL_CURSORS, message, :connection => :reader)
288
300
  end
289
301
  @cursor_id = 0
290
302
  @closed = true
@@ -320,7 +332,10 @@ module Mongo
320
332
  :order => @order,
321
333
  :hint => @hint,
322
334
  :snapshot => @snapshot,
323
- :timeout => @timeout }
335
+ :timeout => @timeout,
336
+ :max_scan => @max_scan,
337
+ :return_key => @return_key,
338
+ :show_disk_loc => @show_disk_loc }
324
339
  end
325
340
 
326
341
  # Clean output for inspect.
@@ -429,6 +444,9 @@ module Mongo
429
444
  spec['$hint'] = @hint if @hint && @hint.length > 0
430
445
  spec['$explain'] = true if @explain
431
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
432
450
  spec
433
451
  end
434
452
 
@@ -251,26 +251,28 @@ module Mongo
251
251
  # new collection. If +strict+ is true, will raise an error if
252
252
  # collection +name+ already exists.
253
253
  #
254
- # @param [String] name the name of the new collection.
254
+ # @param [String, Symbol] name the name of the new collection.
255
255
  #
256
256
  # @option opts [Boolean] :capped (False) created a capped collection.
257
257
  #
258
- # @option opts [Integer] :size (Nil) If +capped+ is +true+, specifies the maximum number of
259
- # bytes for the capped collection. If +false+, specifies the number of bytes allocated
258
+ # @option opts [Integer] :size (Nil) If +capped+ is +true+,
259
+ # specifies the maximum number of bytes for the capped collection.
260
+ # If +false+, specifies the number of bytes allocated
260
261
  # for the initial extent of the collection.
261
262
  #
262
- # @option opts [Integer] :max (Nil) If +capped+ is +true+, indicates the maximum number of records
263
- # in a capped collection.
263
+ # @option opts [Integer] :max (Nil) If +capped+ is +true+, indicates
264
+ # the maximum number of records in a capped collection.
264
265
  #
265
- # @raise [MongoDBError] raised under two conditions: either we're in +strict+ mode and the collection
266
+ # @raise [MongoDBError] raised under two conditions:
267
+ # either we're in +strict+ mode and the collection
266
268
  # already exists or collection creation fails on the server.
267
269
  #
268
270
  # @return [Mongo::Collection]
269
271
  def create_collection(name, opts={})
270
- # Does the collection already exist?
271
- if collection_names.include?(name)
272
+ if collection_names.include?(name.to_s)
272
273
  if strict?
273
- raise MongoDBError, "Collection #{name} already exists. Currently in strict mode."
274
+ raise MongoDBError, "Collection #{name} already exists. " +
275
+ "Currently in strict mode."
274
276
  else
275
277
  return Collection.new(name, self, opts)
276
278
  end
@@ -286,16 +288,19 @@ module Mongo
286
288
 
287
289
  # Get a collection by name.
288
290
  #
289
- # @param [String] name the collection name.
291
+ # @param [String, Symbol] name the collection name.
290
292
  # @param [Hash] opts any valid options that can be passed to Collection#new.
291
293
  #
292
- # @raise [MongoDBError] if collection does not already exist and we're in +strict+ mode.
294
+ # @raise [MongoDBError] if collection does not already exist and we're in
295
+ # +strict+ mode.
293
296
  #
294
297
  # @return [Mongo::Collection]
295
298
  def collection(name, opts={})
296
- if strict? && !collection_names.include?(name)
297
- raise Mongo::MongoDBError, "Collection #{name} doesn't exist. Currently in strict mode."
299
+ if strict? && !collection_names.include?(name.to_s)
300
+ raise Mongo::MongoDBError, "Collection #{name} doesn't exist. " +
301
+ "Currently in strict mode."
298
302
  else
303
+ opts = opts.dup
299
304
  opts[:safe] = opts.fetch(:safe, @safe)
300
305
  opts.merge!(:pk => @pk_factory) unless opts[:pk]
301
306
  Collection.new(name, self, opts)
@@ -305,11 +310,11 @@ module Mongo
305
310
 
306
311
  # Drop a collection by +name+.
307
312
  #
308
- # @param [String] name
313
+ # @param [String, Symbol] name
309
314
  #
310
315
  # @return [Boolean] +true+ on success or +false+ if the collection name doesn't exist.
311
316
  def drop_collection(name)
312
- return true unless collection_names.include?(name)
317
+ return true unless collection_names.include?(name.to_s)
313
318
 
314
319
  ok?(command(:drop => name))
315
320
  end
@@ -590,11 +595,16 @@ module Mongo
590
595
  # @raise [MongoDBError] if the command fails or there's a problem with the validation
591
596
  # data, or if the collection is invalid.
592
597
  def validate_collection(name)
593
- doc = command({:validate => name}, :check_response => false)
594
- raise MongoDBError, "Error with validate command: #{doc.inspect}" unless ok?(doc)
595
- result = doc['result']
596
- raise MongoDBError, "Error with validation data: #{doc.inspect}" unless result.kind_of?(String)
597
- raise MongoDBError, "Error: invalid collection #{name}: #{doc.inspect}" if result =~ /\b(exception|corrupt)\b/i
598
+ cmd = BSON::OrderedHash.new
599
+ cmd[:validate] = name
600
+ cmd[:full] = true
601
+ doc = command(cmd, :check_response => false)
602
+ if !ok?(doc)
603
+ raise MongoDBError, "Error with validate command: #{doc.inspect}"
604
+ end
605
+ if (doc.has_key?('valid') && !doc['valid']) || (doc['result'] =~ /\b(exception|corrupt)\b/i)
606
+ raise MongoDBError, "Error: invalid collection #{name}: #{doc.inspect}"
607
+ end
598
608
  doc
599
609
  end
600
610
 
@@ -65,7 +65,8 @@ module Mongo
65
65
  #
66
66
  # @return [Mongo::ObjectId] the file's id.
67
67
  def put(data, opts={})
68
- filename = opts.delete :filename
68
+ opts = opts.dup
69
+ filename = opts[:filename]
69
70
  opts.merge!(default_grid_io_opts)
70
71
  file = GridIO.new(@files, @chunks, filename, 'w', opts=opts)
71
72
  file.write(data)
@@ -93,6 +93,7 @@ module Mongo
93
93
  #
94
94
  # @return [Mongo::GridIO]
95
95
  def open(filename, mode, opts={})
96
+ opts = opts.dup
96
97
  opts.merge!(default_grid_io_opts(filename))
97
98
  del = opts.delete(:delete_old) && mode == 'w'
98
99
  file = GridIO.new(@files, @chunks, filename, mode, opts)
@@ -55,6 +55,7 @@ module Mongo
55
55
  @chunks = chunks
56
56
  @filename = filename
57
57
  @mode = mode
58
+ opts = opts.dup
58
59
  @query = opts.delete(:query) || {}
59
60
  @query_opts = opts.delete(:query_opts) || {}
60
61
  @fs_name = opts.delete(:fs_name) || Grid::DEFAULT_FS_NAME
@@ -207,43 +208,9 @@ module Mongo
207
208
  elsif separator.is_a?(Integer)
208
209
  read_length(separator)
209
210
  elsif separator.length > 1
210
- result = ''
211
- len = 0
212
- match_idx = 0
213
- match_num = separator.length - 1
214
- to_match = separator[match_idx].chr
215
- if length
216
- matcher = lambda {|idx, num| idx < num && len < length }
217
- else
218
- matcher = lambda {|idx, num| idx < num}
219
- end
220
- while matcher.call(match_idx, match_num) && char = getc
221
- result << char
222
- len += 1
223
- if char == to_match
224
- while match_idx < match_num do
225
- match_idx += 1
226
- to_match = separator[match_idx].chr
227
- char = getc
228
- result << char
229
- if char != to_match
230
- match_idx = 0
231
- to_match = separator[match_idx].chr
232
- break
233
- end
234
- end
235
- end
236
- end
237
- result
211
+ read_to_string(separator, length)
238
212
  else
239
- result = ''
240
- len = 0
241
- while char = getc
242
- result << char
243
- len += 1
244
- break if char == separator || (length ? len >= length : false)
245
- end
246
- result
213
+ read_to_character(separator, length)
247
214
  end
248
215
  end
249
216
 
@@ -284,8 +251,9 @@ module Mongo
284
251
  # @return [Mongo::GridIO] self
285
252
  def each
286
253
  return read_all unless block_given?
287
- while chunk = read(chunk_size)
254
+ while chunk = read(chunk_size)
288
255
  yield chunk
256
+ break if chunk.empty?
289
257
  end
290
258
  self
291
259
  end
@@ -316,21 +284,18 @@ module Mongo
316
284
  chunk
317
285
  end
318
286
 
319
- def last_chunk_number
320
- (@file_length / @chunk_size).to_i
321
- end
322
-
323
287
  # Read a file in its entirety.
324
288
  def read_all
325
289
  buf = ''
326
290
  if @current_chunk
327
291
  buf << @current_chunk['data'].to_s
328
- while chunk = get_chunk(@current_chunk['n'] + 1)
329
- buf << chunk['data'].to_s
330
- @current_chunk = chunk
292
+ while buf.size < @file_length
293
+ @current_chunk = get_chunk(@current_chunk['n'] + 1)
294
+ break if @current_chunk.nil?
295
+ buf << @current_chunk['data'].to_s
331
296
  end
297
+ @file_position = @file_length
332
298
  end
333
- @file_position = @file_length
334
299
  buf
335
300
  end
336
301
 
@@ -361,6 +326,48 @@ module Mongo
361
326
  buf
362
327
  end
363
328
 
329
+ def read_to_character(character="\n", length=nil)
330
+ result = ''
331
+ len = 0
332
+ while char = getc
333
+ result << char
334
+ len += 1
335
+ break if char == character || (length ? len >= length : false)
336
+ end
337
+ result.length > 0 ? result : nil
338
+ end
339
+
340
+ def read_to_string(string="\n", length=nil)
341
+ result = ''
342
+ len = 0
343
+ match_idx = 0
344
+ match_num = string.length - 1
345
+ to_match = string[match_idx].chr
346
+ if length
347
+ matcher = lambda {|idx, num| idx < num && len < length }
348
+ else
349
+ matcher = lambda {|idx, num| idx < num}
350
+ end
351
+ while matcher.call(match_idx, match_num) && char = getc
352
+ result << char
353
+ len += 1
354
+ if char == to_match
355
+ while match_idx < match_num do
356
+ match_idx += 1
357
+ to_match = string[match_idx].chr
358
+ char = getc
359
+ result << char
360
+ if char != to_match
361
+ match_idx = 0
362
+ to_match = string[match_idx].chr
363
+ break
364
+ end
365
+ end
366
+ end
367
+ end
368
+ result.length > 0 ? result : nil
369
+ end
370
+
364
371
  def cache_chunk_data
365
372
  @current_chunk_data = @current_chunk['data'].to_s
366
373
  if @current_chunk_data.respond_to?(:force_encoding)
@@ -413,6 +420,7 @@ module Mongo
413
420
 
414
421
  # Initialize the class for writing a file.
415
422
  def init_write(opts)
423
+ opts = opts.dup
416
424
  @files_id = opts.delete(:_id) || BSON::ObjectId.new
417
425
  @content_type = opts.delete(:content_type) || (defined? MIME) && get_content_type || DEFAULT_CONTENT_TYPE
418
426
  @chunk_size = opts.delete(:chunk_size) || DEFAULT_CHUNK_SIZE
@@ -455,7 +463,9 @@ module Mongo
455
463
  @server_md5 = @files.db.command(md5_command)['md5']
456
464
  if @safe
457
465
  @client_md5 = @local_md5.hexdigest
458
- if @local_md5 != @server_md5
466
+ if @local_md5 == @server_md5
467
+ @server_md5
468
+ else
459
469
  raise GridMD5Failure, "File on server failed MD5 check"
460
470
  end
461
471
  else