mongodb-mongo 0.10.1 → 0.11

Sign up to get free protection for your applications and to get access to all the features.
@@ -26,6 +26,24 @@ module XGen
26
26
  attr_reader :db, :name, :hint
27
27
 
28
28
  def initialize(db, name)
29
+ case name
30
+ when Symbol, String
31
+ else
32
+ raise RuntimeError, "new_name must be a string or symbol"
33
+ end
34
+
35
+ name = name.to_s
36
+
37
+ if name.empty? or name.include? ".."
38
+ raise RuntimeError, "collection names cannot be empty"
39
+ end
40
+ if name.include? "$" and not name.match(/^\$cmd/)
41
+ raise RuntimeError, "collection names must not contain '$'"
42
+ end
43
+ if name.match(/^\./) or name.match(/\.$/)
44
+ raise RuntimeError, "collection names must not start or end with '.'"
45
+ end
46
+
29
47
  @db, @name = db, name
30
48
  @hint = nil
31
49
  end
@@ -192,6 +210,35 @@ EOS
192
210
  }))["result"]
193
211
  end
194
212
 
213
+ # Rename this collection.
214
+ #
215
+ # If operating in auth mode, client must be authorized as an admin to
216
+ # perform this operation. Raises an error if +new_name+ is an invalid
217
+ # collection name.
218
+ #
219
+ # :new_name :: new name for this collection
220
+ def rename(new_name)
221
+ case new_name
222
+ when Symbol, String
223
+ else
224
+ raise RuntimeError, "new_name must be a string or symbol"
225
+ end
226
+
227
+ new_name = new_name.to_s
228
+
229
+ if new_name.empty? or new_name.include? ".."
230
+ raise RuntimeError, "collection names cannot be empty"
231
+ end
232
+ if new_name.include? "$"
233
+ raise RuntimeError, "collection names must not contain '$'"
234
+ end
235
+ if new_name.match(/^\./) or new_name.match(/\.$/)
236
+ raise RuntimeError, "collection names must not start or end with '.'"
237
+ end
238
+
239
+ @db.rename_collection(@name, new_name)
240
+ end
241
+
195
242
  # Get information on the indexes for the collection +collection_name+.
196
243
  # Returns a hash where the keys are index names (as returned by
197
244
  # Collection#create_index and the values are lists of [key, direction]
data/lib/mongo/cursor.rb CHANGED
@@ -31,8 +31,8 @@ module XGen
31
31
 
32
32
  attr_reader :db, :collection, :query
33
33
 
34
- def initialize(db, collection, query)
35
- @db, @collection, @query = db, collection, query
34
+ def initialize(db, collection, query, admin=false)
35
+ @db, @collection, @query, @admin = db, collection, query, admin
36
36
  @num_to_return = @query.number_to_return || 0
37
37
  @cache = []
38
38
  @closed = false
@@ -194,8 +194,10 @@ module XGen
194
194
  def refill_via_get_more
195
195
  send_query_if_needed
196
196
  return if @cursor_id == 0
197
- @db.send_to_db(GetMoreMessage.new(@db.name, @collection.name, @cursor_id))
198
- read_all
197
+ @db._synchronize {
198
+ @db.send_to_db(GetMoreMessage.new(@admin ? 'admin' : @db.name, @collection.name, @cursor_id))
199
+ read_all
200
+ }
199
201
  end
200
202
 
201
203
  def object_from_stream
@@ -212,9 +214,11 @@ module XGen
212
214
  def send_query_if_needed
213
215
  # Run query first time we request an object from the wire
214
216
  unless @query_run
215
- @db.send_query_message(QueryMessage.new(@db.name, @collection.name, @query))
216
- @query_run = true
217
- read_all
217
+ @db._synchronize {
218
+ @db.send_query_message(QueryMessage.new(@admin ? 'admin' : @db.name, @collection.name, @query))
219
+ @query_run = true
220
+ read_all
221
+ }
218
222
  end
219
223
  end
220
224
 
data/lib/mongo/db.rb CHANGED
@@ -173,12 +173,11 @@ module XGen
173
173
  raise "error logging out: #{doc.inspect}" unless ok?(doc)
174
174
  end
175
175
 
176
- # Returns an array of collection names. Each name is of the form
177
- # "database_name.collection_name".
176
+ # Returns an array of collection names in this database.
178
177
  def collection_names
179
178
  names = collections_info.collect { |doc| doc['name'] || '' }
180
- names.delete('')
181
- names
179
+ names = names.delete_if {|name| name.index(@name).nil? || name.index('$')}
180
+ names.map {|name| name.sub(@name + '.', '')}
182
181
  end
183
182
 
184
183
  # Returns a cursor over query result hashes. Each hash contains a
@@ -205,7 +204,7 @@ module XGen
205
204
  # :max :: Max number of records in a capped collection. Optional.
206
205
  def create_collection(name, options={})
207
206
  # First check existence
208
- if collection_names.include?(full_coll_name(name))
207
+ if collection_names.include?(name)
209
208
  if strict?
210
209
  raise "Collection #{name} already exists. Currently in strict mode."
211
210
  else
@@ -230,14 +229,14 @@ module XGen
230
229
  # new collection. If +strict+ is true, will raise an error if
231
230
  # collection +name+ does not already exists.
232
231
  def collection(name)
233
- return Collection.new(self, name) if !strict? || collection_names.include?(full_coll_name(name))
232
+ return Collection.new(self, name) if !strict? || collection_names.include?(name)
234
233
  raise "Collection #{name} doesn't exist. Currently in strict mode."
235
234
  end
236
235
 
237
236
  # Drop collection +name+. Returns +true+ on success or if the
238
237
  # collection does not exist, +false+ otherwise.
239
238
  def drop_collection(name)
240
- return true unless collection_names.include?(full_coll_name(name))
239
+ return true unless collection_names.include?(name)
241
240
 
242
241
  ok?(db_command(:drop => name))
243
242
  end
@@ -340,21 +339,19 @@ module XGen
340
339
  # Note that the query gets sent lazily; the cursor calls
341
340
  # #send_query_message when needed. If the caller never requests an
342
341
  # object from the cursor, the query never gets sent.
343
- def query(collection, query)
344
- Cursor.new(self, collection, query)
342
+ def query(collection, query, admin=false)
343
+ Cursor.new(self, collection, query, admin)
345
344
  end
346
345
 
347
346
  # Used by a Cursor to lazily send the query to the database.
348
347
  def send_query_message(query_message)
349
- @semaphore.synchronize {
350
- send_to_db(query_message)
351
- }
348
+ send_to_db(query_message)
352
349
  end
353
350
 
354
351
  # Remove the records that match +selector+ from +collection_name+.
355
352
  # Normally called by Collection#remove or Collection#clear.
356
353
  def remove_from_db(collection_name, selector)
357
- @semaphore.synchronize {
354
+ _synchronize {
358
355
  send_to_db(RemoveMessage.new(@name, collection_name, selector))
359
356
  }
360
357
  end
@@ -362,7 +359,7 @@ module XGen
362
359
  # Update records in +collection_name+ that match +selector+ by
363
360
  # applying +obj+ as an update. Normally called by Collection#replace.
364
361
  def replace_in_db(collection_name, selector, obj)
365
- @semaphore.synchronize {
362
+ _synchronize {
366
363
  send_to_db(UpdateMessage.new(@name, collection_name, selector, obj, false))
367
364
  }
368
365
  end
@@ -374,7 +371,7 @@ module XGen
374
371
  # applying +obj+ as an update. If no match, inserts (???). Normally
375
372
  # called by Collection#repsert.
376
373
  def repsert_in_db(collection_name, selector, obj)
377
- @semaphore.synchronize {
374
+ _synchronize {
378
375
  obj = @pk_factory.create_pk(obj) if @pk_factory
379
376
  send_to_db(UpdateMessage.new(@name, collection_name, selector, obj, true))
380
377
  obj
@@ -416,6 +413,16 @@ module XGen
416
413
  raise "Error with eval command: #{doc.inspect}"
417
414
  end
418
415
 
416
+ # Rename collection +from+ to +to+. Meant to be called by
417
+ # Collection#rename.
418
+ def rename_collection(from, to)
419
+ oh = OrderedHash.new
420
+ oh[:renameCollection] = "#{@name}.#{from}"
421
+ oh[:to] = "#{@name}.#{to}"
422
+ doc = db_command(oh, true)
423
+ raise "Error renaming collection: #{doc.inspect}" unless ok?(doc)
424
+ end
425
+
419
426
  # Drop index +name+ from +collection_name+. Normally called from
420
427
  # Collection#drop_index or Collection#drop_indexes.
421
428
  def drop_index(collection_name, name)
@@ -460,7 +467,7 @@ module XGen
460
467
  :key => field_h,
461
468
  :unique => unique
462
469
  }
463
- @semaphore.synchronize {
470
+ _synchronize {
464
471
  send_to_db(InsertMessage.new(@name, SYSTEM_INDEX_COLLECTION, false, sel))
465
472
  }
466
473
  name
@@ -470,7 +477,7 @@ module XGen
470
477
  # Collection#insert. Returns a new array containing +objects+,
471
478
  # possibly modified by @pk_factory.
472
479
  def insert_into_db(collection_name, objects)
473
- @semaphore.synchronize {
480
+ _synchronize {
474
481
  if @pk_factory
475
482
  objects.collect! { |o|
476
483
  @pk_factory.create_pk(o)
@@ -511,7 +518,7 @@ module XGen
511
518
  # that the "command" key be first.
512
519
  #
513
520
  # Do not call this. Intended for driver use only.
514
- def db_command(selector)
521
+ def db_command(selector, use_admin_db=false)
515
522
  if !selector.kind_of?(OrderedHash)
516
523
  if !selector.kind_of?(Hash) || selector.keys.length > 1
517
524
  raise "db_command must be given an OrderedHash when there is more than one key"
@@ -520,7 +527,11 @@ module XGen
520
527
 
521
528
  q = Query.new(selector)
522
529
  q.number_to_return = 1
523
- query(Collection.new(self, SYSTEM_COMMAND_COLLECTION), q).next_object
530
+ query(Collection.new(self, SYSTEM_COMMAND_COLLECTION), q, use_admin_db).next_object
531
+ end
532
+
533
+ def _synchronize &block
534
+ @semaphore.synchronize &block
524
535
  end
525
536
 
526
537
  private
@@ -47,6 +47,7 @@ class BSON
47
47
  CODE_W_SCOPE = 15
48
48
  NUMBER_INT = 16
49
49
  TIMESTAMP = 17
50
+ NUMBER_LONG = 18
50
51
  MAXKEY = 127
51
52
 
52
53
  if RUBY_VERSION >= '1.9'
@@ -180,6 +181,9 @@ class BSON
180
181
  when NUMBER_INT
181
182
  key = deserialize_cstr(@buf)
182
183
  doc[key] = deserialize_number_int_data(@buf)
184
+ when NUMBER_LONG
185
+ key = deserialize_cstr(@buf)
186
+ doc[key] = deserialize_number_long_data(@buf)
183
187
  when OID
184
188
  key = deserialize_cstr(@buf)
185
189
  doc[key] = deserialize_oid_data(@buf)
@@ -263,6 +267,12 @@ class BSON
263
267
  unsigned >= 2**32 / 2 ? unsigned - 2**32 : unsigned
264
268
  end
265
269
 
270
+ def deserialize_number_long_data(buf)
271
+ # same note as above applies here...
272
+ unsigned = buf.get_long
273
+ unsigned >= 2 ** 64 / 2 ? unsigned - 2**64 : unsigned
274
+ end
275
+
266
276
  def deserialize_object_data(buf)
267
277
  size = buf.get_int
268
278
  buf.position -= 4
@@ -394,15 +404,23 @@ class BSON
394
404
  end
395
405
 
396
406
  def serialize_number_element(buf, key, val, type)
397
- buf.put(type)
398
- self.class.serialize_cstr(buf, key)
399
407
  if type == NUMBER
408
+ buf.put(type)
409
+ self.class.serialize_cstr(buf, key)
400
410
  buf.put_double(val)
401
411
  else
412
+ if val > 2**64 / 2 - 1 or val < -2**64 / 2
413
+ raise RangeError.new("MongoDB can only handle 8-byte ints")
414
+ end
402
415
  if val > 2**32 / 2 - 1 or val < -2**32 / 2
403
- raise RangeError.new("MongoDB can only handle 4-byte ints - try converting to a double before saving")
416
+ buf.put(NUMBER_LONG)
417
+ self.class.serialize_cstr(buf, key)
418
+ buf.put_long(val)
419
+ else
420
+ buf.put(type)
421
+ self.class.serialize_cstr(buf, key)
422
+ buf.put_int(val)
404
423
  end
405
- buf.put_int(val)
406
424
  end
407
425
  end
408
426
 
@@ -75,11 +75,12 @@ TEST_FILES = ['tests/mongo-qa/_common.rb',
75
75
  'tests/test_mongo.rb',
76
76
  'tests/test_objectid.rb',
77
77
  'tests/test_ordered_hash.rb',
78
+ 'tests/test_threading.rb',
78
79
  'tests/test_round_trip.rb']
79
80
 
80
81
  Gem::Specification.new do |s|
81
82
  s.name = 'mongo'
82
- s.version = '0.10.1'
83
+ s.version = '0.11'
83
84
  s.platform = Gem::Platform::RUBY
84
85
  s.summary = 'Ruby driver for the 10gen Mongo DB'
85
86
  s.description = 'A Ruby driver for the 10gen Mongo DB. For more information about Mongo, see http://www.mongodb.org.'
data/tests/test_bson.rb CHANGED
@@ -215,18 +215,32 @@ class BSONTest < Test::Unit::TestCase
215
215
  end
216
216
 
217
217
  def test_overflow
218
- doc = {"x" => 2**45}
218
+ doc = {"x" => 2**75}
219
219
  assert_raise RangeError do
220
220
  @b.serialize(doc)
221
221
  end
222
222
 
223
- doc = {"x" => 2147483647}
223
+ doc = {"x" => 9223372036854775}
224
+ assert_equal doc, @b.deserialize(@b.serialize(doc).to_a)
225
+
226
+ doc = {"x" => 9223372036854775807}
224
227
  assert_equal doc, @b.deserialize(@b.serialize(doc).to_a)
225
228
 
226
229
  doc["x"] = doc["x"] + 1
227
230
  assert_raise RangeError do
228
231
  @b.serialize(doc)
229
232
  end
233
+
234
+ doc = {"x" => -9223372036854775}
235
+ assert_equal doc, @b.deserialize(@b.serialize(doc).to_a)
236
+
237
+ doc = {"x" => -9223372036854775808}
238
+ assert_equal doc, @b.deserialize(@b.serialize(doc).to_a)
239
+
240
+ doc["x"] = doc["x"] - 1
241
+ assert_raise RangeError do
242
+ @b.serialize(doc)
243
+ end
230
244
  end
231
245
 
232
246
  def test_do_not_change_original_object
data/tests/test_db.rb CHANGED
@@ -51,6 +51,18 @@ class DBTest < Test::Unit::TestCase
51
51
  assert_equal 'ruby-mongo-test.test', @@db.full_coll_name(coll.name)
52
52
  end
53
53
 
54
+ def test_collection_names
55
+ @@db.collection("test").insert("foo" => 5)
56
+ @@db.collection("test.mike").insert("bar" => 0)
57
+
58
+ colls = @@db.collection_names()
59
+ assert colls.include?("test")
60
+ assert colls.include?("test.mike")
61
+ colls.each { |name|
62
+ assert !name.include?("$")
63
+ }
64
+ end
65
+
54
66
  def test_pair
55
67
  @@db.close
56
68
  @@users = nil
data/tests/test_db_api.rb CHANGED
@@ -241,25 +241,25 @@ class DBAPITest < Test::Unit::TestCase
241
241
 
242
242
  def test_drop_collection
243
243
  assert @@db.drop_collection(@@coll.name), "drop of collection #{@@coll.name} failed"
244
- assert !@@db.collection_names.include?(@@coll_full_name)
244
+ assert !@@db.collection_names.include?(@@coll.name)
245
245
  end
246
246
 
247
247
  def test_other_drop
248
- assert @@db.collection_names.include?(@@coll_full_name)
248
+ assert @@db.collection_names.include?(@@coll.name)
249
249
  @@coll.drop
250
- assert !@@db.collection_names.include?(@@coll_full_name)
250
+ assert !@@db.collection_names.include?(@@coll.name)
251
251
  end
252
252
 
253
253
  def test_collection_names
254
254
  names = @@db.collection_names
255
255
  assert names.length >= 1
256
- assert names.include?(@@coll_full_name)
256
+ assert names.include?(@@coll.name)
257
257
 
258
258
  coll2 = @@db.collection('test2')
259
259
  coll2.insert('a' => 1) # collection not created until it's used
260
260
  names = @@db.collection_names
261
261
  assert names.length >= 2
262
- assert names.include?(@@coll_full_name)
262
+ assert names.include?(@@coll.name)
263
263
  assert names.include?('ruby-mongo-test.test2')
264
264
  ensure
265
265
  @@db.drop_collection('test2')
@@ -639,6 +639,28 @@ class DBAPITest < Test::Unit::TestCase
639
639
  assert_equal 2, @@coll.count
640
640
  end
641
641
 
642
+ def test_save_long
643
+ @@coll.clear
644
+ @@coll.insert("x" => 9223372036854775807)
645
+ assert_equal 9223372036854775807, @@coll.find_first()["x"]
646
+ end
647
+
648
+ def test_find_by_oid
649
+ @@coll.clear
650
+
651
+ @@coll.save("hello" => "mike")
652
+ id = @@coll.save("hello" => "world")
653
+ assert_kind_of ObjectID, id
654
+
655
+ assert_equal "world", @@coll.find_first(:_id => id)["hello"]
656
+ @@coll.find(:_id => id).to_a.each do |doc|
657
+ assert_equal "world", doc["hello"]
658
+ end
659
+
660
+ id = ObjectID.from_string(id.to_s)
661
+ assert_equal "world", @@coll.find_first(:_id => id)["hello"]
662
+ end
663
+
642
664
  def test_save_with_object_that_has_id_but_does_not_actually_exist_in_collection
643
665
  @@coll.clear
644
666
 
@@ -691,6 +713,74 @@ class DBAPITest < Test::Unit::TestCase
691
713
  @@coll.modify({"hello" => "world"}, {"$inc" => "hello"})
692
714
  end
693
715
 
716
+ def test_collection_names
717
+ assert_raise RuntimeError do
718
+ @@db.collection(5)
719
+ end
720
+ assert_raise RuntimeError do
721
+ @@db.collection("")
722
+ end
723
+ assert_raise RuntimeError do
724
+ @@db.collection("te$t")
725
+ end
726
+ assert_raise RuntimeError do
727
+ @@db.collection(".test")
728
+ end
729
+ assert_raise RuntimeError do
730
+ @@db.collection("test.")
731
+ end
732
+ assert_raise RuntimeError do
733
+ @@db.collection("tes..t")
734
+ end
735
+ end
736
+
737
+ def test_rename_collection
738
+ @@db.drop_collection("foo")
739
+ @@db.drop_collection("bar")
740
+ a = @@db.collection("foo")
741
+ b = @@db.collection("bar")
742
+
743
+ assert_raise RuntimeError do
744
+ a.rename(5)
745
+ end
746
+ assert_raise RuntimeError do
747
+ a.rename("")
748
+ end
749
+ assert_raise RuntimeError do
750
+ a.rename("te$t")
751
+ end
752
+ assert_raise RuntimeError do
753
+ a.rename(".test")
754
+ end
755
+ assert_raise RuntimeError do
756
+ a.rename("test.")
757
+ end
758
+ assert_raise RuntimeError do
759
+ a.rename("tes..t")
760
+ end
761
+
762
+ assert_equal 0, a.count()
763
+ assert_equal 0, b.count()
764
+
765
+ a.insert("x" => 1)
766
+ a.insert("x" => 2)
767
+
768
+ assert_equal 2, a.count()
769
+
770
+ a.rename("bar")
771
+
772
+ assert_equal 0, a.count()
773
+ assert_equal 2, b.count()
774
+
775
+ assert_equal 1, b.find().to_a()[0]["x"]
776
+ assert_equal 2, b.find().to_a()[1]["x"]
777
+
778
+ b.rename(:foo)
779
+
780
+ assert_equal 2, a.count()
781
+ assert_equal 0, b.count()
782
+ end
783
+
694
784
  # TODO this test fails with error message "Undefed Before end of object"
695
785
  # That is a database error. The undefined type may go away.
696
786
 
@@ -0,0 +1,37 @@
1
+ $LOAD_PATH[0,0] = File.join(File.dirname(__FILE__), '..', 'lib')
2
+ require 'mongo'
3
+ require 'test/unit'
4
+
5
+ class TestThreading < Test::Unit::TestCase
6
+
7
+ include XGen::Mongo::Driver
8
+
9
+ @@host = ENV['MONGO_RUBY_DRIVER_HOST'] || 'localhost'
10
+ @@port = ENV['MONGO_RUBY_DRIVER_PORT'] || Mongo::DEFAULT_PORT
11
+ @@db = Mongo.new(@@host, @@port).db('ruby-mongo-test')
12
+ @@coll = @@db.collection('thread-test-collection')
13
+
14
+ def test_threading
15
+ @@coll.clear
16
+
17
+ 1000.times do |i|
18
+ @@coll.insert("x" => i)
19
+ end
20
+
21
+ threads = []
22
+
23
+ 10.times do |i|
24
+ threads[i] = Thread.new{
25
+ sum = 0
26
+ @@coll.find().each { |document|
27
+ sum += document["x"]
28
+ }
29
+ assert_equal 499500, sum
30
+ }
31
+ end
32
+
33
+ 10.times do |i|
34
+ threads[i].join
35
+ end
36
+ end
37
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mongodb-mongo
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.10.1
4
+ version: "0.11"
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jim Menard
@@ -130,4 +130,5 @@ test_files:
130
130
  - tests/test_mongo.rb
131
131
  - tests/test_objectid.rb
132
132
  - tests/test_ordered_hash.rb
133
+ - tests/test_threading.rb
133
134
  - tests/test_round_trip.rb