mongo 0.18.1 → 0.18.2
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.
- data/README.rdoc +3 -59
- data/Rakefile +7 -13
- data/bin/perf.rb +30 -0
- data/examples/cursor.rb +4 -4
- data/examples/types.rb +1 -1
- data/lib/mongo.rb +2 -2
- data/lib/mongo/admin.rb +1 -2
- data/lib/mongo/collection.rb +85 -45
- data/lib/mongo/connection.rb +55 -102
- data/lib/mongo/constants.rb +3 -3
- data/lib/mongo/cursor.rb +48 -44
- data/lib/mongo/db.rb +8 -8
- data/lib/mongo/errors.rb +8 -2
- data/lib/mongo/gridfs/chunk.rb +0 -1
- data/lib/mongo/gridfs/grid_store.rb +57 -14
- data/lib/mongo/types/code.rb +1 -0
- data/lib/mongo/types/objectid.rb +5 -6
- data/lib/mongo/util/bson_ruby.rb +25 -15
- data/lib/mongo/util/conversions.rb +12 -4
- data/lib/mongo/util/ordered_hash.rb +18 -0
- data/lib/mongo/util/server_version.rb +3 -3
- data/test/replica/count_test.rb +3 -3
- data/test/replica/insert_test.rb +6 -6
- data/test/replica/pooled_insert_test.rb +8 -8
- data/test/replica/query_test.rb +3 -3
- data/test/test_bson.rb +32 -0
- data/test/test_collection.rb +140 -65
- data/test/test_connection.rb +2 -2
- data/test/test_conversions.rb +3 -3
- data/test/test_cursor.rb +44 -20
- data/test/test_db_api.rb +7 -1
- data/test/test_grid_store.rb +16 -2
- data/test/test_objectid.rb +12 -0
- data/test/test_ordered_hash.rb +16 -0
- data/test/test_threading.rb +3 -3
- data/test/threading/test_threading_large_pool.rb +7 -7
- data/test/unit/collection_test.rb +7 -7
- data/test/unit/connection_test.rb +0 -79
- data/test/unit/cursor_test.rb +12 -12
- data/test/unit/db_test.rb +11 -11
- metadata +7 -5
- data/bin/autoreconnect.rb +0 -26
data/lib/mongo/db.rb
CHANGED
@@ -48,7 +48,7 @@ module Mongo
|
|
48
48
|
|
49
49
|
# The Mongo::Connection instance connecting to the MongoDB server.
|
50
50
|
attr_reader :connection
|
51
|
-
|
51
|
+
|
52
52
|
# An array of [host, port] pairs.
|
53
53
|
attr_reader :nodes
|
54
54
|
|
@@ -132,7 +132,7 @@ module Mongo
|
|
132
132
|
names.map {|name| name.sub(@name + '.', '')}
|
133
133
|
end
|
134
134
|
|
135
|
-
#
|
135
|
+
# Retuns an array of Collection instances, one for each collection in this
|
136
136
|
# database.
|
137
137
|
def collections
|
138
138
|
collection_names.map do |collection_name|
|
@@ -316,7 +316,7 @@ module Mongo
|
|
316
316
|
def create_index(collection_name, field_or_spec, unique=false)
|
317
317
|
self.collection(collection_name).create_index(field_or_spec, unique)
|
318
318
|
end
|
319
|
-
|
319
|
+
|
320
320
|
# Return +true+ if +doc+ contains an 'ok' field with the value 1.
|
321
321
|
def ok?(doc)
|
322
322
|
ok = doc['ok']
|
@@ -334,9 +334,9 @@ module Mongo
|
|
334
334
|
end
|
335
335
|
|
336
336
|
cursor = Cursor.new(Collection.new(self, SYSTEM_COMMAND_COLLECTION), :admin => use_admin_db, :limit => -1, :selector => selector, :socket => sock)
|
337
|
-
cursor.
|
337
|
+
cursor.next_document
|
338
338
|
end
|
339
|
-
|
339
|
+
|
340
340
|
# Sends a command to the database.
|
341
341
|
#
|
342
342
|
# :selector (required) :: An OrderedHash, or a standard Hash with just one
|
@@ -352,12 +352,12 @@ module Mongo
|
|
352
352
|
# any selector containing more than one key must be an OrderedHash.
|
353
353
|
def command(selector, admin=false, check_response=false, sock=nil)
|
354
354
|
raise MongoArgumentError, "command must be given a selector" unless selector.is_a?(Hash) && !selector.empty?
|
355
|
-
if selector.class.eql?(Hash) && selector.keys.length > 1
|
355
|
+
if selector.class.eql?(Hash) && selector.keys.length > 1
|
356
356
|
raise MongoArgumentError, "DB#command requires an OrderedHash when hash contains multiple keys"
|
357
357
|
end
|
358
358
|
|
359
|
-
result = Cursor.new(system_command_collection, :admin => admin,
|
360
|
-
:limit => -1, :selector => selector, :socket => sock).
|
359
|
+
result = Cursor.new(system_command_collection, :admin => admin,
|
360
|
+
:limit => -1, :selector => selector, :socket => sock).next_document
|
361
361
|
|
362
362
|
if check_response && !ok?(result)
|
363
363
|
raise OperationFailure, "Database command '#{selector.keys.first}' failed."
|
data/lib/mongo/errors.rb
CHANGED
@@ -30,16 +30,22 @@ module Mongo
|
|
30
30
|
# Raised when given a string is not valid utf-8 (Ruby 1.8 only).
|
31
31
|
class InvalidStringEncoding < MongoRubyError; end
|
32
32
|
|
33
|
+
# Raised when attempting to initialize an invalid ObjectID.
|
34
|
+
class InvalidObjectID < MongoRubyError; end
|
35
|
+
|
33
36
|
# Raised on failures in connection to the database server.
|
34
37
|
class ConnectionError < MongoRubyError; end
|
35
38
|
|
36
39
|
# Raised on failures in connection to the database server.
|
37
40
|
class ConnectionTimeoutError < MongoRubyError; end
|
38
41
|
|
42
|
+
# Raised when trying to insert a document that exceeds the 4MB limit.
|
43
|
+
class InvalidDocument < MongoDBError; end
|
44
|
+
|
39
45
|
# Raised when a database operation fails.
|
40
46
|
class OperationFailure < MongoDBError; end
|
41
|
-
|
42
|
-
# Raised when a
|
47
|
+
|
48
|
+
# Raised when a connection operation fails.
|
43
49
|
class ConnectionFailure < MongoDBError; end
|
44
50
|
|
45
51
|
# Raised when a client attempts to perform an invalid operation.
|
data/lib/mongo/gridfs/chunk.rb
CHANGED
@@ -54,6 +54,9 @@ module GridFS
|
|
54
54
|
# Default is DEFAULT_CONTENT_TYPE
|
55
55
|
attr_accessor :content_type
|
56
56
|
|
57
|
+
# Size of file in bytes
|
58
|
+
attr_reader :length
|
59
|
+
|
57
60
|
attr_accessor :metadata
|
58
61
|
|
59
62
|
attr_reader :files_id
|
@@ -70,7 +73,7 @@ module GridFS
|
|
70
73
|
class << self
|
71
74
|
|
72
75
|
def exist?(db, name, root_collection=DEFAULT_ROOT_COLLECTION)
|
73
|
-
db.collection("#{root_collection}.files").find({'filename' => name}).
|
76
|
+
db.collection("#{root_collection}.files").find({'filename' => name}).next_document != nil
|
74
77
|
end
|
75
78
|
|
76
79
|
def open(db, name, mode, options={})
|
@@ -91,12 +94,12 @@ module GridFS
|
|
91
94
|
}
|
92
95
|
end
|
93
96
|
|
94
|
-
# List the
|
97
|
+
# List the contents of all GridFS files stored in the given db and
|
95
98
|
# root collection.
|
96
99
|
#
|
97
100
|
# :db :: the database to use
|
98
101
|
#
|
99
|
-
# :root_collection :: the root collection to use
|
102
|
+
# :root_collection :: the root collection to use. If not specified, will use default root collection.
|
100
103
|
def list(db, root_collection=DEFAULT_ROOT_COLLECTION)
|
101
104
|
db.collection("#{root_collection}.files").find().map { |f|
|
102
105
|
f['filename']
|
@@ -145,7 +148,7 @@ module GridFS
|
|
145
148
|
@db, @filename, @mode = db, name, mode
|
146
149
|
@root = options[:root] || DEFAULT_ROOT_COLLECTION
|
147
150
|
|
148
|
-
doc = collection.find({'filename' => @filename}).
|
151
|
+
doc = collection.find({'filename' => @filename}).next_document
|
149
152
|
if doc
|
150
153
|
@files_id = doc['_id']
|
151
154
|
@content_type = doc['contentType']
|
@@ -242,7 +245,7 @@ module GridFS
|
|
242
245
|
str
|
243
246
|
end
|
244
247
|
|
245
|
-
def
|
248
|
+
def old_read(len=nil, buf=nil)
|
246
249
|
buf ||= ''
|
247
250
|
byte = self.getc
|
248
251
|
while byte != nil && (len == nil || len > 0)
|
@@ -253,6 +256,14 @@ module GridFS
|
|
253
256
|
buf
|
254
257
|
end
|
255
258
|
|
259
|
+
def read(len=nil, buf=nil)
|
260
|
+
if len
|
261
|
+
read_partial(len, buf)
|
262
|
+
else
|
263
|
+
read_all(buf)
|
264
|
+
end
|
265
|
+
end
|
266
|
+
|
256
267
|
def readchar
|
257
268
|
byte = self.getc
|
258
269
|
raise EOFError.new if byte == nil
|
@@ -331,15 +342,21 @@ module GridFS
|
|
331
342
|
write(obj.to_s)
|
332
343
|
end
|
333
344
|
|
334
|
-
# Writes +string+ as bytes and returns the number of bytes written.
|
335
345
|
def write(string)
|
336
346
|
raise "#@filename not opened for write" unless @mode[0] == ?w
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
|
342
|
-
|
347
|
+
to_write = string.length
|
348
|
+
while (to_write > 0) do
|
349
|
+
if @curr_chunk && @curr_chunk.data.position == @chunk_size
|
350
|
+
prev_chunk_number = @curr_chunk.chunk_number
|
351
|
+
@curr_chunk = GridFS::Chunk.new(self, 'n' => prev_chunk_number + 1)
|
352
|
+
end
|
353
|
+
chunk_available = @chunk_size - @curr_chunk.data.position
|
354
|
+
step_size = (to_write > chunk_available) ? chunk_available : to_write
|
355
|
+
@curr_chunk.data.put_array(ByteBuffer.new(string[-to_write,step_size]).to_a)
|
356
|
+
to_write -= step_size
|
357
|
+
@curr_chunk.save
|
358
|
+
end
|
359
|
+
string.length - to_write
|
343
360
|
end
|
344
361
|
|
345
362
|
# A no-op.
|
@@ -396,7 +413,7 @@ module GridFS
|
|
396
413
|
|
397
414
|
def tell
|
398
415
|
@position
|
399
|
-
|
416
|
+
end
|
400
417
|
|
401
418
|
#---
|
402
419
|
# ================ closing ================
|
@@ -446,13 +463,39 @@ module GridFS
|
|
446
463
|
h
|
447
464
|
end
|
448
465
|
|
466
|
+
def read_partial(len, buf=nil)
|
467
|
+
buf ||= ''
|
468
|
+
byte = self.getc
|
469
|
+
while byte != nil && (len == nil || len > 0)
|
470
|
+
buf << byte.chr
|
471
|
+
len -= 1 if len
|
472
|
+
byte = self.getc if (len == nil || len > 0)
|
473
|
+
end
|
474
|
+
buf
|
475
|
+
end
|
476
|
+
|
477
|
+
def read_all(buf=nil)
|
478
|
+
buf ||= ''
|
479
|
+
while true do
|
480
|
+
if (@curr_chunk.pos > 0)
|
481
|
+
data = @curr_chunk.data.to_s
|
482
|
+
buf += data[@position, data.length]
|
483
|
+
else
|
484
|
+
buf += @curr_chunk.data.to_s
|
485
|
+
end
|
486
|
+
break if @curr_chunk.chunk_number == last_chunk_number
|
487
|
+
@curr_chunk = nth_chunk(@curr_chunk.chunk_number + 1)
|
488
|
+
end
|
489
|
+
buf
|
490
|
+
end
|
491
|
+
|
449
492
|
def delete_chunks
|
450
493
|
chunk_collection.remove({'files_id' => @files_id}) if @files_id
|
451
494
|
@curr_chunk = nil
|
452
495
|
end
|
453
496
|
|
454
497
|
def nth_chunk(n)
|
455
|
-
mongo_chunk = chunk_collection.find({'files_id' => @files_id, 'n' => n}).
|
498
|
+
mongo_chunk = chunk_collection.find({'files_id' => @files_id, 'n' => n}).next_document
|
456
499
|
Chunk.new(self, mongo_chunk || {})
|
457
500
|
end
|
458
501
|
|
data/lib/mongo/types/code.rb
CHANGED
data/lib/mongo/types/objectid.rb
CHANGED
@@ -44,7 +44,7 @@ module Mongo
|
|
44
44
|
|
45
45
|
# Adds a primary key to the given document if needed.
|
46
46
|
def self.create_pk(doc)
|
47
|
-
doc
|
47
|
+
doc.has_key?(:_id) || doc.has_key?('_id') ? doc : doc.merge!(:_id => self.new)
|
48
48
|
end
|
49
49
|
|
50
50
|
# +data+ is an array of bytes. If nil, a new id will be generated.
|
@@ -70,7 +70,7 @@ module Mongo
|
|
70
70
|
# Given a string representation of an ObjectID, return a new ObjectID
|
71
71
|
# with that value.
|
72
72
|
def self.from_string(str)
|
73
|
-
raise "illegal ObjectID format" unless legal?(str)
|
73
|
+
raise InvalidObjectID, "illegal ObjectID format" unless legal?(str)
|
74
74
|
data = []
|
75
75
|
12.times do |i|
|
76
76
|
data[i] = str[i * 2, 2].to_i(16)
|
@@ -83,7 +83,7 @@ module Mongo
|
|
83
83
|
# removed. If you are not sure that you need this method you should be
|
84
84
|
# using the regular from_string.
|
85
85
|
def self.from_string_legacy(str)
|
86
|
-
raise "illegal ObjectID format" unless legal?(str)
|
86
|
+
raise InvalidObjectID, "illegal ObjectID format" unless legal?(str)
|
87
87
|
data = []
|
88
88
|
BYTE_ORDER.each_with_index { |string_position, data_index|
|
89
89
|
data[data_index] = str[string_position * 2, 2].to_i(16)
|
@@ -132,9 +132,8 @@ module Mongo
|
|
132
132
|
|
133
133
|
private
|
134
134
|
|
135
|
-
|
136
|
-
|
137
|
-
rescue LoadError
|
135
|
+
# We need to define this method only if CBson isn't loaded.
|
136
|
+
unless defined? CBson
|
138
137
|
def generate
|
139
138
|
oid = ''
|
140
139
|
|
data/lib/mongo/util/bson_ruby.rb
CHANGED
@@ -72,6 +72,11 @@ class BSON_RUBY
|
|
72
72
|
buf.put_array(to_utf8(val.to_s).unpack("C*") << 0)
|
73
73
|
end
|
74
74
|
|
75
|
+
def self.serialize_key(buf, key)
|
76
|
+
raise InvalidDocument, "Key names / regex patterns must not contain the NULL byte" if key.include? "\x00"
|
77
|
+
self.serialize_cstr(buf, key)
|
78
|
+
end
|
79
|
+
|
75
80
|
def to_a
|
76
81
|
@buf.to_a
|
77
82
|
end
|
@@ -107,6 +112,9 @@ class BSON_RUBY
|
|
107
112
|
obj.each {|k, v| serialize_key_value(k, v, check_keys) unless k == '_id' || k == :_id }
|
108
113
|
|
109
114
|
serialize_eoo_element(@buf)
|
115
|
+
if @buf.size > 4 * 1024 * 1024
|
116
|
+
raise InvalidDocument, "Document is too large (#{@buf.size}). BSON documents are limited to 4MB (#{4 * 1024 * 1024})."
|
117
|
+
end
|
110
118
|
@buf.put_int(@buf.size, 0)
|
111
119
|
self
|
112
120
|
end
|
@@ -362,7 +370,7 @@ class BSON_RUBY
|
|
362
370
|
|
363
371
|
def serialize_null_element(buf, key)
|
364
372
|
buf.put(NULL)
|
365
|
-
self.class.
|
373
|
+
self.class.serialize_key(buf, key)
|
366
374
|
end
|
367
375
|
|
368
376
|
def serialize_dbref_element(buf, key, val)
|
@@ -374,7 +382,7 @@ class BSON_RUBY
|
|
374
382
|
|
375
383
|
def serialize_binary_element(buf, key, val)
|
376
384
|
buf.put(BINARY)
|
377
|
-
self.class.
|
385
|
+
self.class.serialize_key(buf, key)
|
378
386
|
|
379
387
|
bytes = val.to_a
|
380
388
|
num_bytes = bytes.length
|
@@ -393,13 +401,13 @@ class BSON_RUBY
|
|
393
401
|
|
394
402
|
def serialize_boolean_element(buf, key, val)
|
395
403
|
buf.put(BOOLEAN)
|
396
|
-
self.class.
|
404
|
+
self.class.serialize_key(buf, key)
|
397
405
|
buf.put(val ? 1 : 0)
|
398
406
|
end
|
399
407
|
|
400
408
|
def serialize_date_element(buf, key, val)
|
401
409
|
buf.put(DATE)
|
402
|
-
self.class.
|
410
|
+
self.class.serialize_key(buf, key)
|
403
411
|
millisecs = (val.to_f * 1000).to_i
|
404
412
|
buf.put_long(millisecs)
|
405
413
|
end
|
@@ -407,7 +415,7 @@ class BSON_RUBY
|
|
407
415
|
def serialize_number_element(buf, key, val, type)
|
408
416
|
if type == NUMBER
|
409
417
|
buf.put(type)
|
410
|
-
self.class.
|
418
|
+
self.class.serialize_key(buf, key)
|
411
419
|
buf.put_double(val)
|
412
420
|
else
|
413
421
|
if val > 2**64 / 2 - 1 or val < -2**64 / 2
|
@@ -415,11 +423,11 @@ class BSON_RUBY
|
|
415
423
|
end
|
416
424
|
if val > 2**32 / 2 - 1 or val < -2**32 / 2
|
417
425
|
buf.put(NUMBER_LONG)
|
418
|
-
self.class.
|
426
|
+
self.class.serialize_key(buf, key)
|
419
427
|
buf.put_long(val)
|
420
428
|
else
|
421
429
|
buf.put(type)
|
422
|
-
self.class.
|
430
|
+
self.class.serialize_key(buf, key)
|
423
431
|
buf.put_int(val)
|
424
432
|
end
|
425
433
|
end
|
@@ -427,7 +435,7 @@ class BSON_RUBY
|
|
427
435
|
|
428
436
|
def serialize_object_element(buf, key, val, check_keys, opcode=OBJECT)
|
429
437
|
buf.put(opcode)
|
430
|
-
self.class.
|
438
|
+
self.class.serialize_key(buf, key)
|
431
439
|
buf.put_array(BSON.new.serialize(val, check_keys).to_a)
|
432
440
|
end
|
433
441
|
|
@@ -441,10 +449,12 @@ class BSON_RUBY
|
|
441
449
|
|
442
450
|
def serialize_regex_element(buf, key, val)
|
443
451
|
buf.put(REGEX)
|
444
|
-
self.class.
|
452
|
+
self.class.serialize_key(buf, key)
|
445
453
|
|
446
|
-
str = val.
|
447
|
-
|
454
|
+
str = val.source
|
455
|
+
# We use serialize_key here since regex patterns aren't prefixed with
|
456
|
+
# length (can't contain the NULL byte).
|
457
|
+
self.class.serialize_key(buf, str)
|
448
458
|
|
449
459
|
options = val.options
|
450
460
|
options_str = ''
|
@@ -458,14 +468,14 @@ class BSON_RUBY
|
|
458
468
|
|
459
469
|
def serialize_oid_element(buf, key, val)
|
460
470
|
buf.put(OID)
|
461
|
-
self.class.
|
471
|
+
self.class.serialize_key(buf, key)
|
462
472
|
|
463
473
|
buf.put_array(val.to_a)
|
464
474
|
end
|
465
475
|
|
466
476
|
def serialize_string_element(buf, key, val, type)
|
467
477
|
buf.put(type)
|
468
|
-
self.class.
|
478
|
+
self.class.serialize_key(buf, key)
|
469
479
|
|
470
480
|
# Make a hole for the length
|
471
481
|
len_pos = buf.position
|
@@ -485,7 +495,7 @@ class BSON_RUBY
|
|
485
495
|
|
486
496
|
def serialize_code_w_scope(buf, key, val)
|
487
497
|
buf.put(CODE_W_SCOPE)
|
488
|
-
self.class.
|
498
|
+
self.class.serialize_key(buf, key)
|
489
499
|
|
490
500
|
# Make a hole for the length
|
491
501
|
len_pos = buf.position
|
@@ -544,7 +554,7 @@ class BSON_RUBY
|
|
544
554
|
when Symbol
|
545
555
|
SYMBOL
|
546
556
|
else
|
547
|
-
raise "Unknown type of object: #{o.class.name}"
|
557
|
+
raise InvalidDocument, "Unknown type of object: #{o.class.name}"
|
548
558
|
end
|
549
559
|
end
|
550
560
|
|
@@ -32,11 +32,19 @@ module Mongo #:nodoc:
|
|
32
32
|
# <tt>{ "field1" => 1, "field2" => -1}</tt>
|
33
33
|
def array_as_sort_parameters(value)
|
34
34
|
order_by = OrderedHash.new
|
35
|
-
value.
|
36
|
-
|
37
|
-
|
35
|
+
if value.first.is_a? Array
|
36
|
+
value.each do |param|
|
37
|
+
if (param.class.name == "String")
|
38
|
+
order_by[param] = 1
|
39
|
+
else
|
40
|
+
order_by[param[0]] = sort_value(param[1]) unless param[1].nil?
|
41
|
+
end
|
42
|
+
end
|
43
|
+
elsif !value.empty?
|
44
|
+
if order_by.size == 1
|
45
|
+
order_by[value.first] = 1
|
38
46
|
else
|
39
|
-
order_by[
|
47
|
+
order_by[value.first] = sort_value(value[1])
|
40
48
|
end
|
41
49
|
end
|
42
50
|
order_by
|
@@ -111,5 +111,23 @@ class OrderedHash < Hash
|
|
111
111
|
super
|
112
112
|
@ordered_keys = []
|
113
113
|
end
|
114
|
+
|
115
|
+
def hash
|
116
|
+
code = 17
|
117
|
+
each_pair do |key, value|
|
118
|
+
code = 37 * code + key.hash
|
119
|
+
code = 37 * code + value.hash
|
120
|
+
end
|
121
|
+
code
|
122
|
+
end
|
123
|
+
|
124
|
+
def eql?(o)
|
125
|
+
if o.instance_of? OrderedHash
|
126
|
+
self.hash == o.hash
|
127
|
+
else
|
128
|
+
false
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
114
132
|
end
|
115
133
|
end
|