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