mongodb-mongo 0.8 → 0.9

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