mongodb-mongo 0.10.1 → 0.11

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.
@@ -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