mongodb-mongo 0.8 → 0.9

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 CHANGED
@@ -324,6 +324,13 @@ Adrian Madrid, aemadrid@gmail.com
324
324
  Aman Gupta, aman@tmm1.net
325
325
  * Collection#save
326
326
 
327
+ Jon Crosby, jon@joncrosby.me
328
+ * Some code clean-up
329
+
330
+ John Nunemaker, http://railstips.org
331
+ * Collection#create_index takes symbols as well as strings
332
+ * Fix for Collection#save
333
+
327
334
  = License
328
335
 
329
336
  Copyright 2008-2009 10gen Inc.
@@ -76,7 +76,7 @@ module XGen
76
76
  # Save an updated +object+ to the collection, or insert it if it doesn't exist already.
77
77
  def save(object)
78
78
  if id = object[:_id] || object['_id']
79
- modify({:_id => id}, object)
79
+ repsert({:_id => id}, object)
80
80
  else
81
81
  insert(object)
82
82
  end
@@ -171,10 +171,11 @@ function () {
171
171
  key[keys[i]] = obj[keys[i]];
172
172
  }
173
173
 
174
- var aggObj = map[key];
174
+ var aggObj = map.get(key);
175
175
  if (aggObj == null) {
176
176
  var newObj = Object.extend({}, key);
177
- aggObj = map[key] = Object.extend(newObj, initial);
177
+ aggObj = Object.extend(newObj, initial);
178
+ map.put(key, aggObj);
178
179
  }
179
180
  reduce_function(obj, aggObj);
180
181
  }
@@ -190,14 +191,10 @@ EOS
190
191
  }))["result"]
191
192
  end
192
193
 
193
- # Return an array of hashes, one for each index. Each hash contains:
194
- #
195
- # :name :: Index name
196
- #
197
- # :keys :: Hash whose keys are the names of the fields that make up
198
- # the key and values are integers.
199
- #
200
- # :ns :: Namespace; same as this collection's name.
194
+ # Get information on the indexes for the collection +collection_name+.
195
+ # Returns a hash where the keys are index names (as returned by
196
+ # Collection#create_index and the values are lists of [key, direction]
197
+ # pairs specifying the index (as passed to Collection#create_index).
201
198
  def index_information
202
199
  @db.index_information(@name)
203
200
  end
data/lib/mongo/db.rb CHANGED
@@ -390,6 +390,7 @@ module XGen
390
390
  oh[:query] = selector || {}
391
391
  doc = db_command(oh)
392
392
  return doc['n'].to_i if ok?(doc)
393
+ return 0 if doc['errmsg'] == "ns missing"
393
394
  raise "Error with count command: #{doc.inspect}"
394
395
  end
395
396
 
@@ -425,31 +426,18 @@ module XGen
425
426
  raise "Error with drop_index command: #{doc.inspect}" unless ok?(doc)
426
427
  end
427
428
 
428
- # Return an array of hashes, one for each index on +collection_name+.
429
- # Normally called by Collection#index_information. Each hash contains:
430
- #
431
- # :name :: Index name
432
- #
433
- # :keys :: Hash whose keys are the names of the fields that make up
434
- # the key and values are integers.
435
- #
436
- # :ns :: Namespace; same as +collection_name+.
429
+ # Get information on the indexes for the collection +collection_name+.
430
+ # Normally called by Collection#index_information. Returns a hash where
431
+ # the keys are index names (as returned by Collection#create_index and
432
+ # the values are lists of [key, direction] pairs specifying the index
433
+ # (as passed to Collection#create_index).
437
434
  def index_information(collection_name)
438
435
  sel = {:ns => full_coll_name(collection_name)}
439
- query(Collection.new(self, SYSTEM_INDEX_COLLECTION), Query.new(sel)).collect { |row|
440
- h = {:name => row['name']}
441
- raise "Name of index on return from db was nil. Coll = #{full_coll_name(collection_name)}" unless h[:name]
442
-
443
- h[:keys] = row['key']
444
- raise "Keys for index on return from db was nil. Coll = #{full_coll_name(collection_name)}" unless h[:keys]
445
-
446
- h[:ns] = row['ns']
447
- raise "Namespace for index on return from db was nil. Coll = #{full_coll_name(collection_name)}" unless h[:ns]
448
- h[:ns].sub!(/.*\./, '')
449
- raise "Error: ns != collection" unless h[:ns] == collection_name
450
-
451
- h
436
+ info = {}
437
+ query(Collection.new(self, SYSTEM_INDEX_COLLECTION), Query.new(sel)).each { |index|
438
+ info[index['name']] = index['key'].to_a
452
439
  }
440
+ info
453
441
  end
454
442
 
455
443
  # Create a new index on +collection_name+. +field_or_spec+
@@ -460,10 +448,10 @@ module XGen
460
448
  # enforce a uniqueness constraint.
461
449
  def create_index(collection_name, field_or_spec, unique=false)
462
450
  field_h = OrderedHash.new
463
- if field_or_spec.is_a? String
464
- field_h[field_or_spec] = 1
451
+ if field_or_spec.is_a?(String) || field_or_spec.is_a?(Symbol)
452
+ field_h[field_or_spec.to_s] = 1
465
453
  else
466
- field_or_spec.each { |f| field_h[f[0]] = f[1] }
454
+ field_or_spec.each { |f| field_h[f[0].to_s] = f[1] }
467
455
  end
468
456
  name = gen_index_name(field_h)
469
457
  sel = {
@@ -473,7 +461,7 @@ module XGen
473
461
  :unique => unique
474
462
  }
475
463
  @semaphore.synchronize {
476
- send_to_db(InsertMessage.new(@name, SYSTEM_INDEX_COLLECTION, sel))
464
+ send_to_db(InsertMessage.new(@name, SYSTEM_INDEX_COLLECTION, false, sel))
477
465
  }
478
466
  name
479
467
  end
@@ -483,11 +471,13 @@ module XGen
483
471
  # possibly modified by @pk_factory.
484
472
  def insert_into_db(collection_name, objects)
485
473
  @semaphore.synchronize {
486
- objects.collect { |o|
487
- o = @pk_factory.create_pk(o) if @pk_factory
488
- send_to_db(InsertMessage.new(@name, collection_name, o))
489
- o
490
- }
474
+ if @pk_factory
475
+ objects.collect! { |o|
476
+ @pk_factory.create_pk(o)
477
+ }
478
+ end
479
+ send_to_db(InsertMessage.new(@name, collection_name, true, *objects))
480
+ objects
491
481
  }
492
482
  end
493
483
 
@@ -23,11 +23,11 @@ module XGen
23
23
 
24
24
  class InsertMessage < Message
25
25
 
26
- def initialize(db_name, collection_name, *objs)
26
+ def initialize(db_name, collection_name, check_keys=true, *objs)
27
27
  super(OP_INSERT)
28
28
  write_int(0)
29
29
  write_string("#{db_name}.#{collection_name}")
30
- objs.each { |o| write_doc(o) }
30
+ objs.each { |o| write_doc(o, check_keys) }
31
31
  end
32
32
  end
33
33
  end
@@ -36,7 +36,7 @@ module XGen
36
36
  @request_id = (@@class_req_id += 1)
37
37
  @response_id = 0
38
38
  @buf = ByteBuffer.new
39
-
39
+
40
40
  @buf.put_int(16) # holder for length
41
41
  @buf.put_int(@request_id)
42
42
  @buf.put_int(0) # response_to
@@ -58,8 +58,8 @@ module XGen
58
58
  update_message_length
59
59
  end
60
60
 
61
- def write_doc(hash)
62
- @buf.put_array(BSON.new.serialize(hash).to_a)
61
+ def write_doc(hash, check_keys=false)
62
+ @buf.put_array(BSON.new.serialize(hash, check_keys).to_a)
63
63
  update_message_length
64
64
  end
65
65
 
@@ -73,11 +73,11 @@ class BSON
73
73
 
74
74
  begin
75
75
  require 'mongo_ext/cbson'
76
- def serialize(obj)
77
- @buf = ByteBuffer.new(CBson.serialize(obj))
76
+ def serialize(obj, check_keys=false)
77
+ @buf = ByteBuffer.new(CBson.serialize(obj, check_keys))
78
78
  end
79
79
  rescue LoadError
80
- def serialize(obj)
80
+ def serialize(obj, check_keys=false)
81
81
  raise "Document is null" unless obj
82
82
 
83
83
  @buf.rewind
@@ -86,12 +86,12 @@ class BSON
86
86
 
87
87
  # Write key/value pairs. Always write _id first if it exists.
88
88
  if obj.has_key? '_id'
89
- serialize_key_value('_id', obj['_id'])
89
+ serialize_key_value('_id', obj['_id'], check_keys)
90
90
  elsif obj.has_key? :_id
91
- serialize_key_value('_id', obj[:_id])
91
+ serialize_key_value('_id', obj[:_id], check_keys)
92
92
  end
93
93
 
94
- obj.each {|k, v| serialize_key_value(k, v) unless k == '_id' || k == :_id }
94
+ obj.each {|k, v| serialize_key_value(k, v, check_keys) unless k == '_id' || k == :_id }
95
95
 
96
96
  serialize_eoo_element(@buf)
97
97
  @buf.put_int(@buf.size, 0)
@@ -99,38 +99,47 @@ class BSON
99
99
  end
100
100
  end
101
101
 
102
- def serialize_key_value(k, v)
103
- type = bson_type(v)
104
- case type
105
- when STRING, SYMBOL
106
- serialize_string_element(@buf, k, v, type)
107
- when NUMBER, NUMBER_INT
108
- serialize_number_element(@buf, k, v, type)
109
- when OBJECT
110
- serialize_object_element(@buf, k, v)
111
- when OID
112
- serialize_oid_element(@buf, k, v)
113
- when ARRAY
114
- serialize_array_element(@buf, k, v)
115
- when REGEX
116
- serialize_regex_element(@buf, k, v)
117
- when BOOLEAN
118
- serialize_boolean_element(@buf, k, v)
119
- when DATE
120
- serialize_date_element(@buf, k, v)
121
- when NULL
122
- serialize_null_element(@buf, k)
123
- when REF
124
- serialize_dbref_element(@buf, k, v)
125
- when BINARY
126
- serialize_binary_element(@buf, k, v)
127
- when UNDEFINED
128
- serialize_undefined_element(@buf, k)
129
- when CODE_W_SCOPE
130
- serialize_code_w_scope(@buf, k, v)
131
- else
132
- raise "unhandled type #{type}"
102
+ def serialize_key_value(k, v, check_keys)
103
+ k = k.to_s
104
+ if check_keys
105
+ if k[0] == ?$
106
+ raise RuntimeError.new("key #{k} must not start with '$'")
107
+ end
108
+ if k.include? ?.
109
+ raise RuntimeError.new("key #{k} must not contain '.'")
133
110
  end
111
+ end
112
+ type = bson_type(v)
113
+ case type
114
+ when STRING, SYMBOL
115
+ serialize_string_element(@buf, k, v, type)
116
+ when NUMBER, NUMBER_INT
117
+ serialize_number_element(@buf, k, v, type)
118
+ when OBJECT
119
+ serialize_object_element(@buf, k, v, check_keys)
120
+ when OID
121
+ serialize_oid_element(@buf, k, v)
122
+ when ARRAY
123
+ serialize_array_element(@buf, k, v, check_keys)
124
+ when REGEX
125
+ serialize_regex_element(@buf, k, v)
126
+ when BOOLEAN
127
+ serialize_boolean_element(@buf, k, v)
128
+ when DATE
129
+ serialize_date_element(@buf, k, v)
130
+ when NULL
131
+ serialize_null_element(@buf, k)
132
+ when REF
133
+ serialize_dbref_element(@buf, k, v)
134
+ when BINARY
135
+ serialize_binary_element(@buf, k, v)
136
+ when UNDEFINED
137
+ serialize_undefined_element(@buf, k)
138
+ when CODE_W_SCOPE
139
+ serialize_code_w_scope(@buf, k, v)
140
+ else
141
+ raise "unhandled type #{type}"
142
+ end
134
143
  end
135
144
 
136
145
  begin
@@ -344,7 +353,7 @@ class BSON
344
353
  oh = OrderedHash.new
345
354
  oh['$ref'] = val.namespace
346
355
  oh['$id'] = val.object_id
347
- serialize_object_element(buf, key, oh)
356
+ serialize_object_element(buf, key, oh, false)
348
357
  end
349
358
 
350
359
  def serialize_binary_element(buf, key, val)
@@ -391,24 +400,24 @@ class BSON
391
400
  buf.put_double(val)
392
401
  else
393
402
  if val > 2**32 / 2 - 1 or val < -2**32 / 2
394
- raise RangeError.new "MongoDB can only handle 4-byte ints - try converting to a double before saving"
403
+ raise RangeError.new("MongoDB can only handle 4-byte ints - try converting to a double before saving")
395
404
  end
396
405
  buf.put_int(val)
397
406
  end
398
407
  end
399
408
 
400
- def serialize_object_element(buf, key, val, opcode=OBJECT)
409
+ def serialize_object_element(buf, key, val, check_keys, opcode=OBJECT)
401
410
  buf.put(opcode)
402
411
  self.class.serialize_cstr(buf, key)
403
- buf.put_array(BSON.new.serialize(val).to_a)
412
+ buf.put_array(BSON.new.serialize(val, check_keys).to_a)
404
413
  end
405
414
 
406
- def serialize_array_element(buf, key, val)
415
+ def serialize_array_element(buf, key, val, check_keys)
407
416
  # Turn array into hash with integer indices as keys
408
417
  h = OrderedHash.new
409
418
  i = 0
410
419
  val.each { |v| h[i] = v; i += 1 }
411
- serialize_object_element(buf, key, h, ARRAY)
420
+ serialize_object_element(buf, key, h, check_keys, ARRAY)
412
421
  end
413
422
 
414
423
  def serialize_regex_element(buf, key, val)
@@ -79,7 +79,7 @@ TEST_FILES = ['tests/mongo-qa/_common.rb',
79
79
 
80
80
  Gem::Specification.new do |s|
81
81
  s.name = 'mongo'
82
- s.version = '0.8'
82
+ s.version = '0.9'
83
83
  s.platform = Gem::Platform::RUBY
84
84
  s.summary = 'Ruby driver for the 10gen Mongo DB'
85
85
  s.description = 'A Ruby driver for the 10gen Mongo DB. For more information about Mongo, see http://www.mongodb.org.'
data/tests/test_db_api.rb CHANGED
@@ -56,6 +56,11 @@ class DBAPITest < Test::Unit::TestCase
56
56
  assert docs.detect { |row| row['b'] == 3 }
57
57
  end
58
58
 
59
+ def test_count_on_nonexisting
60
+ @@db.drop_collection('foo')
61
+ assert_equal 0, @@db.collection('foo').count()
62
+ end
63
+
59
64
  def test_find_simple
60
65
  @r2 = @@coll.insert('a' => 2)
61
66
  @r3 = @@coll.insert('b' => 3)
@@ -282,29 +287,55 @@ class DBAPITest < Test::Unit::TestCase
282
287
  end
283
288
 
284
289
  def test_index_information
290
+ assert_equal @@coll.index_information.length, 1
291
+
285
292
  name = @@db.create_index(@@coll.name, 'a')
286
- list = @@db.index_information(@@coll.name)
287
- assert_equal @@coll.index_information, list
288
- assert_equal 2, list.length
289
-
290
- info = list[1]
291
- assert_equal name, 'a_1'
292
- assert_equal name, info[:name]
293
- assert_equal 1, info[:keys]['a']
293
+ info = @@db.index_information(@@coll.name)
294
+ assert_equal name, "a_1"
295
+ assert_equal @@coll.index_information, info
296
+ assert_equal 2, info.length
297
+
298
+ assert info.has_key?(name)
299
+ assert_equal info[name], [["a", ASCENDING]]
300
+ ensure
301
+ @@db.drop_index(@@coll.name, name)
302
+ end
303
+
304
+ def test_index_create_with_symbol
305
+ assert_equal @@coll.index_information.length, 1
306
+
307
+ name = @@db.create_index(@@coll.name, :a)
308
+ info = @@db.index_information(@@coll.name)
309
+ assert_equal name, "a_1"
310
+ assert_equal @@coll.index_information, info
311
+ assert_equal 2, info.length
312
+
313
+ assert info.has_key?(name)
314
+ assert_equal info[name], [["a", ASCENDING]]
294
315
  ensure
295
316
  @@db.drop_index(@@coll.name, name)
296
317
  end
297
318
 
298
319
  def test_multiple_index_cols
299
320
  name = @@db.create_index(@@coll.name, [['a', DESCENDING], ['b', ASCENDING], ['c', DESCENDING]])
300
- list = @@db.index_information(@@coll.name)
301
- assert_equal 2, list.length
321
+ info = @@db.index_information(@@coll.name)
322
+ assert_equal 2, info.length
323
+
324
+ assert_equal name, 'a_-1_b_1_c_-1'
325
+ assert info.has_key?(name)
326
+ assert_equal [['a', DESCENDING], ['b', ASCENDING], ['c', DESCENDING]], info[name]
327
+ ensure
328
+ @@db.drop_index(@@coll.name, name)
329
+ end
330
+
331
+ def test_multiple_index_cols_with_symbols
332
+ name = @@db.create_index(@@coll.name, [[:a, DESCENDING], [:b, ASCENDING], [:c, DESCENDING]])
333
+ info = @@db.index_information(@@coll.name)
334
+ assert_equal 2, info.length
302
335
 
303
- info = list[1]
304
336
  assert_equal name, 'a_-1_b_1_c_-1'
305
- assert_equal name, info[:name]
306
- keys = info[:keys].keys
307
- assert_equal ['a', 'b', 'c'], keys.sort
337
+ assert info.has_key?(name)
338
+ assert_equal [['a', DESCENDING], ['b', ASCENDING], ['c', DESCENDING]], info[name]
308
339
  ensure
309
340
  @@db.drop_index(@@coll.name, name)
310
341
  end
@@ -327,8 +358,25 @@ class DBAPITest < Test::Unit::TestCase
327
358
  test.insert("hello" => "mike")
328
359
  test.insert("hello" => "world")
329
360
  assert @@db.error?
361
+ end
362
+
363
+ def test_index_on_subfield
364
+ @@db.drop_collection("blah")
365
+ test = @@db.collection("blah")
366
+
367
+ test.insert("hello" => {"a" => 4, "b" => 5})
368
+ test.insert("hello" => {"a" => 7, "b" => 2})
369
+ test.insert("hello" => {"a" => 4, "b" => 10})
370
+ assert !@@db.error?
330
371
 
331
372
  @@db.drop_collection("blah")
373
+ test = @@db.collection("blah")
374
+ test.create_index("hello.a", unique=true)
375
+
376
+ test.insert("hello" => {"a" => 4, "b" => 5})
377
+ test.insert("hello" => {"a" => 7, "b" => 2})
378
+ test.insert("hello" => {"a" => 4, "b" => 10})
379
+ assert @@db.error?
332
380
  end
333
381
 
334
382
  def test_array
@@ -585,6 +633,58 @@ class DBAPITest < Test::Unit::TestCase
585
633
  assert_equal 2, @@coll.count
586
634
  end
587
635
 
636
+ def test_save_with_object_that_has_id_but_does_not_actually_exist_in_collection
637
+ @@coll.clear
638
+
639
+ a = {'_id' => '1', 'hello' => 'world'}
640
+ @@coll.save(a)
641
+ assert_equal(1, @@coll.count)
642
+ assert_equal("world", @@coll.find_first()["hello"])
643
+
644
+ a["hello"] = "mike"
645
+ @@coll.save(a)
646
+ assert_equal(1, @@coll.count)
647
+ assert_equal("mike", @@coll.find_first()["hello"])
648
+ end
649
+
650
+ def test_invalid_key_names
651
+ @@coll.clear
652
+
653
+ @@coll.insert({"hello" => "world"})
654
+ @@coll.insert({"hello" => {"hello" => "world"}})
655
+
656
+ assert_raise RuntimeError do
657
+ @@coll.insert({"$hello" => "world"})
658
+ end
659
+ assert_raise RuntimeError do
660
+ @@coll.insert({"hello" => {"$hello" => "world"}})
661
+ end
662
+
663
+ @@coll.insert({"he$llo" => "world"})
664
+ @@coll.insert({"hello" => {"hell$o" => "world"}})
665
+
666
+ assert_raise RuntimeError do
667
+ @@coll.insert({".hello" => "world"})
668
+ end
669
+ assert_raise RuntimeError do
670
+ @@coll.insert({"hello" => {".hello" => "world"}})
671
+ end
672
+ assert_raise RuntimeError do
673
+ @@coll.insert({"hello." => "world"})
674
+ end
675
+ assert_raise RuntimeError do
676
+ @@coll.insert({"hello" => {"hello." => "world"}})
677
+ end
678
+ assert_raise RuntimeError do
679
+ @@coll.insert({"hel.lo" => "world"})
680
+ end
681
+ assert_raise RuntimeError do
682
+ @@coll.insert({"hello" => {"hel.lo" => "world"}})
683
+ end
684
+
685
+ @@coll.modify({"hello" => "world"}, {"$inc" => "hello"})
686
+ end
687
+
588
688
  # TODO this test fails with error message "Undefed Before end of object"
589
689
  # That is a database error. The undefined type may go away.
590
690
 
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.8"
4
+ version: "0.9"
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jim Menard