mongo 1.1.5 → 1.2.rc0

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/lib/mongo/db.rb CHANGED
@@ -56,33 +56,33 @@ module Mongo
56
56
 
57
57
  # Instances of DB are normally obtained by calling Mongo#db.
58
58
  #
59
- # @param [String] db_name the database name.
59
+ # @param [String] name the database name.
60
60
  # @param [Mongo::Connection] connection a connection object pointing to MongoDB. Note
61
61
  # that databases are usually instantiated via the Connection class. See the examples below.
62
62
  #
63
- # @option options [Boolean] :strict (False) If true, collections must exist to be accessed and must
63
+ # @option opts [Boolean] :strict (False) If true, collections must exist to be accessed and must
64
64
  # not exist to be created. See DB#collection and DB#create_collection.
65
65
  #
66
- # @option options [Object, #create_pk(doc)] :pk (Mongo::ObjectId) A primary key factory object,
66
+ # @option opts [Object, #create_pk(doc)] :pk (Mongo::ObjectId) A primary key factory object,
67
67
  # which should take a hash and return a hash which merges the original hash with any primary key
68
68
  # fields the factory wishes to inject. (NOTE: if the object already has a primary key,
69
69
  # the factory should not inject a new key).
70
70
  #
71
- # @option options [Boolean, Hash] :safe (false) Set the default safe-mode options
71
+ # @option opts [Boolean, Hash] :safe (false) Set the default safe-mode options
72
72
  # propogated to Collection objects instantiated off of this DB. If no
73
73
  # value is provided, the default value set on this instance's Connection object will be used. This
74
74
  # default can be overridden upon instantiation of any collection by explicity setting a :safe value
75
75
  # on initialization
76
- # @option options [Integer] :cache_time (300) Set the time that all ensure_index calls should cache the command.
76
+ # @option opts [Integer] :cache_time (300) Set the time that all ensure_index calls should cache the command.
77
77
  #
78
78
  # @core databases constructor_details
79
- def initialize(db_name, connection, options={})
80
- @name = Mongo::Support.validate_db_name(db_name)
79
+ def initialize(name, connection, opts={})
80
+ @name = Mongo::Support.validate_db_name(name)
81
81
  @connection = connection
82
- @strict = options[:strict]
83
- @pk_factory = options[:pk]
84
- @safe = options.has_key?(:safe) ? options[:safe] : @connection.safe
85
- @cache_time = options[:cache_time] || 300 #5 minutes.
82
+ @strict = opts[:strict]
83
+ @pk_factory = opts[:pk]
84
+ @safe = opts.fetch(:safe, @connection.safe)
85
+ @cache_time = opts[:cache_time] || 300 #5 minutes.
86
86
  end
87
87
 
88
88
  # Authenticate with the given username and password. Note that mongod
@@ -208,8 +208,8 @@ module Mongo
208
208
  #
209
209
  # @return [Array<Mongo::Collection>]
210
210
  def collections
211
- collection_names.map do |collection_name|
212
- Collection.new(self, collection_name)
211
+ collection_names.map do |name|
212
+ Collection.new(name, self)
213
213
  end
214
214
  end
215
215
 
@@ -223,7 +223,7 @@ module Mongo
223
223
  def collections_info(coll_name=nil)
224
224
  selector = {}
225
225
  selector[:name] = full_collection_name(coll_name) if coll_name
226
- Cursor.new(Collection.new(self, SYSTEM_NAMESPACE_COLLECTION), :selector => selector)
226
+ Cursor.new(Collection.new(SYSTEM_NAMESPACE_COLLECTION, self), :selector => selector)
227
227
  end
228
228
 
229
229
  # Create a collection.
@@ -233,52 +233,52 @@ module Mongo
233
233
  #
234
234
  # @param [String] name the name of the new collection.
235
235
  #
236
- # @option options [Boolean] :capped (False) created a capped collection.
236
+ # @option opts [Boolean] :capped (False) created a capped collection.
237
237
  #
238
- # @option options [Integer] :size (Nil) If +capped+ is +true+, specifies the maximum number of
238
+ # @option opts [Integer] :size (Nil) If +capped+ is +true+, specifies the maximum number of
239
239
  # bytes for the capped collection. If +false+, specifies the number of bytes allocated
240
240
  # for the initial extent of the collection.
241
241
  #
242
- # @option options [Integer] :max (Nil) If +capped+ is +true+, indicates the maximum number of records
242
+ # @option opts [Integer] :max (Nil) If +capped+ is +true+, indicates the maximum number of records
243
243
  # in a capped collection.
244
244
  #
245
245
  # @raise [MongoDBError] raised under two conditions: either we're in +strict+ mode and the collection
246
246
  # already exists or collection creation fails on the server.
247
247
  #
248
248
  # @return [Mongo::Collection]
249
- def create_collection(name, options={})
249
+ def create_collection(name, opts={})
250
250
  # Does the collection already exist?
251
251
  if collection_names.include?(name)
252
252
  if strict?
253
253
  raise MongoDBError, "Collection #{name} already exists. Currently in strict mode."
254
254
  else
255
- return Collection.new(self, name)
255
+ return Collection.new(name, self)
256
256
  end
257
257
  end
258
258
 
259
259
  # Create a new collection.
260
260
  oh = BSON::OrderedHash.new
261
261
  oh[:create] = name
262
- doc = command(oh.merge(options || {}))
263
- return Collection.new(self, name, :pk => @pk_factory) if ok?(doc)
262
+ doc = command(oh.merge(opts || {}))
263
+ return Collection.new(name, self, :pk => @pk_factory) if ok?(doc)
264
264
  raise MongoDBError, "Error creating collection: #{doc.inspect}"
265
265
  end
266
266
 
267
267
  # Get a collection by name.
268
268
  #
269
269
  # @param [String] name the collection name.
270
- # @param [Hash] options any valid options that can me passed to Collection#new.
270
+ # @param [Hash] opts any valid options that can me passed to Collection#new.
271
271
  #
272
272
  # @raise [MongoDBError] if collection does not already exist and we're in +strict+ mode.
273
273
  #
274
274
  # @return [Mongo::Collection]
275
- def collection(name, options={})
275
+ def collection(name, opts={})
276
276
  if strict? && !collection_names.include?(name)
277
277
  raise Mongo::MongoDBError, "Collection #{name} doesn't exist. Currently in strict mode."
278
278
  else
279
- options[:safe] = options.has_key?(:safe) ? options[:safe] : @safe
280
- options.merge!(:pk => @pk_factory) unless options[:pk]
281
- Collection.new(self, name, options)
279
+ opts[:safe] = opts.fetch(:safe, @safe)
280
+ opts.merge!(:pk => @pk_factory) unless opts[:pk]
281
+ Collection.new(name, self, opts)
282
282
  end
283
283
  end
284
284
  alias_method :[], :collection
@@ -403,7 +403,7 @@ module Mongo
403
403
  def drop_index(collection_name, index_name)
404
404
  oh = BSON::OrderedHash.new
405
405
  oh[:deleteIndexes] = collection_name
406
- oh[:index] = index_name
406
+ oh[:index] = index_name.to_s
407
407
  doc = command(oh, :check_response => false)
408
408
  ok?(doc) || raise(MongoDBError, "Error with drop_index command: #{doc.inspect}")
409
409
  end
@@ -418,7 +418,7 @@ module Mongo
418
418
  def index_information(collection_name)
419
419
  sel = {:ns => full_collection_name(collection_name)}
420
420
  info = {}
421
- Cursor.new(Collection.new(self, SYSTEM_INDEX_COLLECTION), :selector => sel).each do |index|
421
+ Cursor.new(Collection.new(SYSTEM_INDEX_COLLECTION, self), :selector => sel).each do |index|
422
422
  info[index['name']] = index
423
423
  end
424
424
  info
@@ -558,7 +558,7 @@ module Mongo
558
558
  #
559
559
  # @return [Array] a list of documents containing profiling information.
560
560
  def profiling_info
561
- Cursor.new(Collection.new(self, DB::SYSTEM_PROFILE_COLLECTION), :selector => {}).to_a
561
+ Cursor.new(Collection.new(SYSTEM_PROFILE_COLLECTION, self), :selector => {}).to_a
562
562
  end
563
563
 
564
564
  # Validate a named collection.
@@ -581,7 +581,7 @@ module Mongo
581
581
  private
582
582
 
583
583
  def system_command_collection
584
- Collection.new(self, SYSTEM_COMMAND_COLLECTION)
584
+ Collection.new(SYSTEM_COMMAND_COLLECTION, self)
585
585
  end
586
586
  end
587
587
  end
@@ -32,6 +32,8 @@ module Mongo
32
32
  # @param [Array] args A list of host-port pairs ending with a hash containing any options. See
33
33
  # the examples below for exactly how to use the constructor.
34
34
  #
35
+ # @option options [String] :rs_name (nil) The name of the replica set to connect to. You
36
+ # can use this option to verify that you're connecting to the right replica set.
35
37
  # @option options [Boolean, Hash] :safe (false) Set the default safe-mode options
36
38
  # propogated to DB objects instantiated off of this Connection. This
37
39
  # default can be overridden upon instantiation of any DB by explicity setting a :safe value
@@ -48,8 +50,10 @@ module Mongo
48
50
  # @example Connect to a replica set and provide two seed nodes:
49
51
  # ReplSetConnection.new(['localhost', 30000], ['localhost', 30001])
50
52
  #
51
- # @example Connect to a replica set providing two seed nodes and allowing reads from a
52
- # secondary node:
53
+ # @example Connect to a replica set providing two seed nodes and ensuring a connection to the replica set named 'prod':
54
+ # ReplSetConnection.new(['localhost', 30000], ['localhost', 30001], :rs_name => 'prod')
55
+ #
56
+ # @example Connect to a replica set providing two seed nodes and allowing reads from a secondary node:
53
57
  # ReplSetConnection.new(['localhost', 30000], ['localhost', 30001], :read_secondary => true)
54
58
  #
55
59
  # @see http://api.mongodb.org/ruby/current/file.REPLICA_SETS.html Replica sets in Ruby
@@ -111,7 +115,9 @@ module Mongo
111
115
 
112
116
  pick_secondary_for_read if @read_secondary
113
117
 
114
- if !connected?
118
+ if connected?
119
+ BSON::BSON_CODER.update_max_bson_size(self)
120
+ else
115
121
  if @secondary_pools.empty?
116
122
  raise ConnectionFailure, "Failed to connect any given host:port"
117
123
  else
@@ -124,6 +130,14 @@ module Mongo
124
130
  @nodes_to_try.length > 0
125
131
  end
126
132
 
133
+ # Determine whether we're reading from a primary node. If false,
134
+ # this connection connects to a secondary node and @read_secondaries is true.
135
+ #
136
+ # @return [Boolean]
137
+ def read_primary?
138
+ !@read_pool || @read_pool.length.zero?
139
+ end
140
+
127
141
  # Close the connection to the database.
128
142
  def close
129
143
  super
@@ -22,14 +22,14 @@ module Mongo
22
22
 
23
23
  # Create a new pool of connections.
24
24
  #
25
- def initialize(connection, host, port, options={})
25
+ def initialize(connection, host, port, opts={})
26
26
  @connection = connection
27
27
 
28
28
  @host, @port = host, port
29
29
 
30
30
  # Pool size and timeout.
31
- @size = options[:size] || 1
32
- @timeout = options[:timeout] || 5.0
31
+ @size = opts[:size] || 1
32
+ @timeout = opts[:timeout] || 5.0
33
33
 
34
34
  # Mutex for synchronizing pool access
35
35
  @connection_mutex = Mutex.new
@@ -17,28 +17,99 @@
17
17
  # ++
18
18
 
19
19
  module Mongo
20
- module URIParser
20
+ class URIParser
21
21
 
22
22
  DEFAULT_PORT = 27017
23
23
  MONGODB_URI_MATCHER = /(([-_.\w\d]+):([-_\w\d]+)@)?([-.\w\d]+)(:([\w\d]+))?(\/([-\d\w]+))?/
24
24
  MONGODB_URI_SPEC = "mongodb://[username:password@]host1[:port1][,host2[:port2],...[,hostN[:portN]]][/database]"
25
+ SPEC_ATTRS = [:nodes, :auths]
26
+ OPT_ATTRS = [:connect, :replicaset, :slaveok, :safe, :w, :wtimeout, :fsync]
25
27
 
26
- extend self
28
+ OPT_VALID = {:connect => lambda {|arg| ['direct', 'replicaset'].include?(arg)},
29
+ :replicaset => lambda {|arg| arg.length > 0},
30
+ :slaveok => lambda {|arg| ['true', 'false'].include?(arg)},
31
+ :safe => lambda {|arg| ['true', 'false'].include?(arg)},
32
+ :w => lambda {|arg| arg =~ /^\d+$/ },
33
+ :wtimeout => lambda {|arg| arg =~ /^\d+$/ },
34
+ :fsync => lambda {|arg| ['true', 'false'].include?(arg)}
35
+ }
36
+
37
+ OPT_ERR = {:connect => "must be 'direct' or 'replicaset'",
38
+ :replicaset => "must be a string containing the name of the replica set to connect to",
39
+ :slaveok => "must be 'true' or 'false'",
40
+ :safe => "must be 'true' or 'false'",
41
+ :w => "must be an integer specifying number of nodes to replica to",
42
+ :wtimeout => "must be an integer specifying milliseconds",
43
+ :fsync => "must be 'true' or 'false'"
44
+ }
45
+
46
+ OPT_CONV = {:connect => lambda {|arg| arg},
47
+ :replicaset => lambda {|arg| arg},
48
+ :slaveok => lambda {|arg| arg == 'true' ? true : false},
49
+ :safe => lambda {|arg| arg == 'true' ? true : false},
50
+ :w => lambda {|arg| arg.to_i},
51
+ :wtimeout => lambda {|arg| arg.to_i},
52
+ :fsync => lambda {|arg| arg == 'true' ? true : false}
53
+ }
54
+
55
+ attr_reader :nodes, :auths, :connect, :replicaset, :slaveok, :safe, :w, :wtimeout, :fsync
27
56
 
28
57
  # Parse a MongoDB URI. This method is used by Connection.from_uri.
29
58
  # Returns an array of nodes and an array of db authorizations, if applicable.
30
59
  #
31
- # @private
32
- def parse(string)
60
+ # @core connections
61
+ def initialize(string)
33
62
  if string =~ /^mongodb:\/\//
34
63
  string = string[10..-1]
35
64
  else
36
65
  raise MongoArgumentError, "MongoDB URI must match this spec: #{MONGODB_URI_SPEC}"
37
66
  end
38
67
 
39
- nodes = []
40
- auths = []
41
- specs = string.split(',')
68
+ hosts, opts = string.split('?')
69
+ parse_hosts(hosts)
70
+ parse_options(opts)
71
+ configure_connect
72
+ end
73
+
74
+ def connection_options
75
+ opts = {}
76
+
77
+ if (@w || @wtimeout || @fsync) && !@safe
78
+ raise MongoArgumentError, "Safe must be true if w, wtimeout, or fsync is specified"
79
+ end
80
+
81
+ if @safe
82
+ if @w || @wtimeout || @fsync
83
+ safe_opts = {}
84
+ safe_opts[:w] = @w if @w
85
+ safe_opts[:wtimeout] = @wtimeout if @wtimeout
86
+ safe_opts[:fsync] = @fsync if @fsync
87
+ else
88
+ safe_opts = true
89
+ end
90
+
91
+ opts[:safe] = safe_opts
92
+ end
93
+
94
+ if @slaveok
95
+ if @connect == 'direct'
96
+ opts[:slave_ok] = true
97
+ else
98
+ opts[:read_secondary] = true
99
+ end
100
+ end
101
+
102
+ opts[:rs_name] = @replicaset if @replicaset
103
+
104
+ opts
105
+ end
106
+
107
+ private
108
+
109
+ def parse_hosts(hosts)
110
+ @nodes = []
111
+ @auths = []
112
+ specs = hosts.split(',')
42
113
  specs.each do |spec|
43
114
  matches = MONGODB_URI_MATCHER.match(spec)
44
115
  if !matches
@@ -52,8 +123,8 @@ module Mongo
52
123
  if !(port.to_s =~ /^\d+$/)
53
124
  raise MongoArgumentError, "Invalid port #{port}; port must be specified as digits."
54
125
  end
55
- port = port.to_i
56
- db = matches[8]
126
+ port = port.to_i
127
+ db = matches[8]
57
128
 
58
129
  if uname && pwd && db
59
130
  auths << {'db_name' => db, 'username' => uname, 'password' => pwd}
@@ -62,10 +133,47 @@ module Mongo
62
133
  "and db if any one of these is specified."
63
134
  end
64
135
 
65
- nodes << [host, port]
136
+ @nodes << [host, port]
137
+ end
138
+ end
139
+
140
+ # This method uses the lambdas defined in OPT_VALID and OPT_CONV to validate
141
+ # and convert the given options.
142
+ def parse_options(opts)
143
+ return unless opts
144
+ separator = opts.include?('&') ? '&' : ';'
145
+ opts.split(separator).each do |attr|
146
+ key, value = attr.split('=')
147
+ key = key.to_sym
148
+ value = value.strip.downcase
149
+ if !OPT_ATTRS.include?(key)
150
+ raise MongoArgumentError, "Invalid Mongo URI option #{key}"
151
+ end
152
+
153
+ if OPT_VALID[key].call(value)
154
+ instance_variable_set("@#{key}", OPT_CONV[key].call(value))
155
+ else
156
+ raise MongoArgumentError, "Invalid value for #{key}: #{OPT_ERR[key]}"
157
+ end
158
+ end
159
+ end
160
+
161
+ def configure_connect
162
+ if @nodes.length > 1 && !@connect
163
+ @connect = 'replicaset'
66
164
  end
67
165
 
68
- [nodes, auths]
166
+ if !@connect
167
+ if @nodes.length > 1
168
+ @connect = 'replicaset'
169
+ else
170
+ @connect = 'direct'
171
+ end
172
+ end
173
+
174
+ if @connect == 'direct' && @replicaset
175
+ raise MongoArgumentError, "If specifying a replica set name, please also specify that connect=replicaset"
176
+ end
69
177
  end
70
178
  end
71
179
  end
@@ -67,13 +67,26 @@ class BSONTest < Test::Unit::TestCase
67
67
  assert_doc_pass(doc)
68
68
  end
69
69
 
70
- def test_document_length
71
- doc = {'name' => 'a' * 5 * 1024 * 1024}
70
+ def test_limit_max_bson_size
71
+ doc = {'name' => 'a' * BSON_CODER.max_bson_size}
72
72
  assert_raise InvalidDocument do
73
73
  assert @encoder.serialize(doc)
74
74
  end
75
75
  end
76
76
 
77
+ def test_max_bson_size
78
+ assert BSON_CODER.max_bson_size >= BSON::DEFAULT_MAX_BSON_SIZE
79
+ end
80
+
81
+ def test_update_max_bson_size
82
+ require 'ostruct'
83
+ mock_conn = OpenStruct.new
84
+ size = 7 * 1024 * 1024
85
+ mock_conn.max_bson_size = size
86
+ assert_equal size, BSON_CODER.update_max_bson_size(mock_conn)
87
+ assert_equal size, BSON_CODER.max_bson_size
88
+ end
89
+
77
90
  def test_round_trip
78
91
  doc = {'doc' => 123}
79
92
  @encoder.deserialize(@encoder.serialize(doc))
@@ -200,6 +213,15 @@ class BSONTest < Test::Unit::TestCase
200
213
  assert_doc_pass(doc)
201
214
  end
202
215
 
216
+ def test_array_keys
217
+ doc = {'doc' => [1, 2, 'a', 'b']}
218
+ bson = @encoder.serialize(doc).to_a
219
+ assert_equal 48, bson[14]
220
+ assert_equal 49, bson[21]
221
+ assert_equal 50, bson[28]
222
+ assert_equal 51, bson[37]
223
+ end
224
+
203
225
  def test_regex
204
226
  doc = {'doc' => /foobar/i}
205
227
  assert_doc_pass(doc)
@@ -32,11 +32,11 @@ class TestCollection < Test::Unit::TestCase
32
32
  end
33
33
 
34
34
  def test_pk_factory_on_collection
35
- @coll = Collection.new(@@db, 'foo', TestPK)
35
+ @coll = Collection.new('foo', @@db, TestPK)
36
36
  assert_equal TestPK, @coll.pk_factory
37
37
 
38
38
 
39
- @coll2 = Collection.new(@@db, 'foo', :pk => TestPK)
39
+ @coll2 = Collection.new('foo', @@db, :pk => TestPK)
40
40
  assert_equal TestPK, @coll2.pk_factory
41
41
  end
42
42
 
@@ -417,7 +417,7 @@ class TestCollection < Test::Unit::TestCase
417
417
 
418
418
  m = "function() { emit(this.user_id, 1); }"
419
419
  r = "function(k,vals) { return 1; }"
420
- res = @@test.map_reduce(m, r);
420
+ res = @@test.map_reduce(m, r, :out => 'foo');
421
421
  assert res.find_one({"_id" => 1})
422
422
  assert res.find_one({"_id" => 2})
423
423
  end
@@ -428,7 +428,7 @@ class TestCollection < Test::Unit::TestCase
428
428
 
429
429
  m = Code.new("function() { emit(this.user_id, 1); }")
430
430
  r = Code.new("function(k,vals) { return 1; }")
431
- res = @@test.map_reduce(m, r);
431
+ res = @@test.map_reduce(m, r, :out => 'foo');
432
432
  assert res.find_one({"_id" => 1})
433
433
  assert res.find_one({"_id" => 2})
434
434
  end
@@ -441,7 +441,7 @@ class TestCollection < Test::Unit::TestCase
441
441
 
442
442
  m = Code.new("function() { emit(this.user_id, 1); }")
443
443
  r = Code.new("function(k,vals) { return 1; }")
444
- res = @@test.map_reduce(m, r, :query => {"user_id" => {"$gt" => 1}});
444
+ res = @@test.map_reduce(m, r, :query => {"user_id" => {"$gt" => 1}}, :out => 'foo');
445
445
  assert_equal 2, res.count
446
446
  assert res.find_one({"_id" => 2})
447
447
  assert res.find_one({"_id" => 3})
@@ -450,7 +450,7 @@ class TestCollection < Test::Unit::TestCase
450
450
  def test_map_reduce_with_raw_response
451
451
  m = Code.new("function() { emit(this.user_id, 1); }")
452
452
  r = Code.new("function(k,vals) { return 1; }")
453
- res = @@test.map_reduce(m, r, :raw => true)
453
+ res = @@test.map_reduce(m, r, :raw => true, :out => 'foo')
454
454
  assert res["result"]
455
455
  assert res["counts"]
456
456
  assert res["timeMillis"]
@@ -600,6 +600,23 @@ class TestCollection < Test::Unit::TestCase
600
600
  @@test.drop_index("a_1")
601
601
  end
602
602
 
603
+ def test_ensure_index_timeout
604
+ @@db.cache_time = 2
605
+ coll = @@db['ensure_test']
606
+ coll.expects(:generate_indexes).twice
607
+ coll.ensure_index([['a', 1]])
608
+
609
+ # These will be cached
610
+ coll.ensure_index([['a', 1]])
611
+ coll.ensure_index([['a', 1]])
612
+ coll.ensure_index([['a', 1]])
613
+ coll.ensure_index([['a', 1]])
614
+
615
+ sleep(3)
616
+ # This won't be, so generate_indexes will be called twice
617
+ coll.ensure_index([['a', 1]])
618
+ end
619
+
603
620
  context "Grouping" do
604
621
  setup do
605
622
  @@test.remove
@@ -693,6 +710,17 @@ class TestCollection < Test::Unit::TestCase
693
710
  @geo = @@db.collection('geo')
694
711
  end
695
712
 
713
+ should "create index using symbols" do
714
+ @collection.create_index :foo, :name => :bar
715
+ @geo.create_index :goo, :name => :baz
716
+ assert @collection.index_information['bar']
717
+ @collection.drop_index :bar
718
+ assert_nil @collection.index_information['bar']
719
+ assert @geo.index_information['baz']
720
+ @geo.drop_index(:baz)
721
+ assert_nil @geo.index_information['baz']
722
+ end
723
+
696
724
  should "create a geospatial index" do
697
725
  @geo.save({'loc' => [-100, 100]})
698
726
  @geo.create_index([['loc', Mongo::GEO2D]])