mongo 1.1.5 → 1.2.rc0

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