mongodb-mongo 0.2.4 → 0.2.5

Sign up to get free protection for your applications and to get access to all the features.
data/README.rdoc CHANGED
@@ -32,6 +32,11 @@ Install the "mongo" gem by typing
32
32
  $ gem sources -a http://gems.github.com
33
33
  $ sudo gem install mongodb-mongo-ruby-driver
34
34
 
35
+ The first line tells RubyGems to add the GitHub gem repository. You only need
36
+ to run this command once.
37
+
38
+ === From the GitHub source
39
+
35
40
  The source code is available at http://github.com/mongodb/mongo-ruby-driver.
36
41
  You can either clone the git repository or download a tarball or zip file.
37
42
  Once you have the source, you can use it from wherever you downloaded it or
data/lib/mongo/cursor.rb CHANGED
@@ -70,7 +70,19 @@ module XGen
70
70
  def next_object
71
71
  refill_via_get_more if num_remaining == 0
72
72
  o = @cache.shift
73
- raise o['$err'] if o && o['$err']
73
+
74
+ if o && o['$err']
75
+ err = o['$err']
76
+
77
+ # If the server has stopped being the master (e.g., it's one of a
78
+ # pair but it has died or something like that) then we close that
79
+ # connection. If the db has auto connect option and a pair of
80
+ # servers, next request will re-open on master server.
81
+ @db.close if err == "not master"
82
+
83
+ raise err
84
+ end
85
+
74
86
  o
75
87
  end
76
88
 
data/lib/mongo/db.rb CHANGED
@@ -61,6 +61,9 @@ module XGen
61
61
  # The database's socket. For internal (and Cursor) use only.
62
62
  attr_reader :socket
63
63
 
64
+ def slave_ok?; @slave_ok; end
65
+ def auto_reconnect?; @auto_reconnect; end
66
+
64
67
  # A primary key factory object (or +nil+). See the README.doc file or
65
68
  # DB#new for details.
66
69
  attr_reader :pk_factory
@@ -70,9 +73,12 @@ module XGen
70
73
  @pk_factory = pk_factory
71
74
  end
72
75
 
76
+ # Instances of DB are normally obtained by calling Mongo#db.
77
+ #
73
78
  # db_name :: The database name
74
79
  #
75
- # nodes :: An array of [host, port] pairs.
80
+ # nodes :: An array of [host, port] pairs. See Mongo#new, which offers
81
+ # a more flexible way of defining nodes.
76
82
  #
77
83
  # options :: A hash of options.
78
84
  #
@@ -92,27 +98,49 @@ module XGen
92
98
  # object's +create_pk+ method will be called and the new hash
93
99
  # returned will be inserted.
94
100
  #
95
- # When a DB object first connects, it tries the first node. If that
96
- # fails, it keeps trying to connect to the remaining nodes until it
97
- # sucessfully connects.
101
+ # :slave_ok :: Only used if +nodes+ contains only one host/port. If
102
+ # false, when connecting to that host/port we check to
103
+ # see if the server is the master. If it is not, an error
104
+ # is thrown.
105
+ #
106
+ # :auto_reconnect :: If the connection gets closed (for example, we
107
+ # have a server pair and saw the "not master"
108
+ # error, which closes the connection), then
109
+ # automatically try to reconnect to the master or
110
+ # to the single server we have been given. Defaults
111
+ # to +false+.
112
+ #
113
+ # When a DB object first connects to a pair, it will find the master
114
+ # instance and connect to that one. On socket error or if we recieve a
115
+ # "not master" error, we again find the master of the pair.
98
116
  def initialize(db_name, nodes, options={})
99
117
  raise "Invalid DB name \"#{db_name}\" (must be non-nil, non-zero-length, and can not contain \".\")" if !db_name || (db_name && db_name.length > 0 && db_name.include?("."))
100
118
  @name, @nodes = db_name, nodes
101
119
  @strict = options[:strict]
102
120
  @pk_factory = options[:pk]
121
+ @slave_ok = options[:slave_ok] && @nodes.length == 1 # only OK if one node
122
+ @auto_reconnect = options[:auto_reconnect]
103
123
  @semaphore = Object.new
104
124
  @semaphore.extend Mutex_m
105
- connect_to_first_available_host
125
+ connect_to_master
106
126
  end
107
127
 
108
- def connect_to_first_available_host
128
+ def connect_to_master
109
129
  close if @socket
110
130
  @host = @port = nil
111
131
  @nodes.detect { |hp|
112
132
  @host, @port = *hp
113
133
  begin
114
134
  @socket = TCPSocket.new(@host, @port)
115
- break if ok?(db_command(:ismaster => 1)) # success
135
+
136
+ # Check for master. Can't call master? because it uses mutex,
137
+ # which may already be in use during this call.
138
+ semaphore_is_locked = @semaphore.locked?
139
+ @semaphore.unlock if semaphore_is_locked
140
+ is_master = master?
141
+ @semaphore.lock if semaphore_is_locked
142
+
143
+ break if @slave_ok || is_master
116
144
  rescue => ex
117
145
  close if @socket
118
146
  end
@@ -214,6 +242,28 @@ module XGen
214
242
  ok?(db_command(:drop => name))
215
243
  end
216
244
 
245
+ # Returns the error message from the most recently executed database
246
+ # operation for this connection, or +nil+ if there was no error.
247
+ #
248
+ # Note: as of this writing, errors are only detected on the db server
249
+ # for certain kinds of operations (writes). The plan is to change this
250
+ # so that all operations will set the error if needed.
251
+ def error
252
+ doc = db_command(:getlasterror => 1)
253
+ raise "error retrieving last error: #{doc}" unless ok?(doc)
254
+ doc['err']
255
+ end
256
+
257
+ # Returns +true+ if there is was an error caused by the most recently
258
+ # executed database operation.
259
+ #
260
+ # Note: as of this writing, errors are only detected on the db server
261
+ # for certain kinds of operations (writes). The plan is to change this
262
+ # so that all operations will set the error if needed.
263
+ def error?
264
+ error != nil
265
+ end
266
+
217
267
  # Returns true if this database is a master (or is not paired with any
218
268
  # other database), false if it is a slave.
219
269
  def master?
@@ -236,20 +286,6 @@ module XGen
236
286
  end
237
287
  end
238
288
 
239
- # Switches our socket to the master database. If we are already the
240
- # master, no change is made.
241
- def switch_to_master
242
- master_str = master()
243
- unless master_str == "#@host:#@port"
244
- @semaphore.synchronize {
245
- master_str =~ /(.+):(\d+)/
246
- @host, @port = $1, $2
247
- close()
248
- @socket = TCPSocket.new(@host, @port)
249
- }
250
- end
251
- end
252
-
253
289
  # Close the connection to the database.
254
290
  def close
255
291
  @socket.close if @socket
@@ -387,7 +423,13 @@ module XGen
387
423
  end
388
424
 
389
425
  def send_to_db(message)
390
- @socket.print(message.buf.to_s)
426
+ connect_to_master if !connected? && @auto_reconnect
427
+ begin
428
+ @socket.print(message.buf.to_s)
429
+ rescue => ex
430
+ close
431
+ raise ex
432
+ end
391
433
  end
392
434
 
393
435
  def full_coll_name(collection_name)
data/lib/mongo/mongo.rb CHANGED
@@ -25,37 +25,73 @@ module XGen
25
25
 
26
26
  DEFAULT_PORT = 27017
27
27
 
28
- # Either nodes_or_host is a host name string and port is an optional
29
- # port number that defaults to DEFAULT_PORT, or nodes_or_host is an
30
- # array of arrays, where each is a host/port pair (or a host with no
31
- # port). Finally, if nodes_or_host is nil then host is 'localhost' and
32
- # port is DEFAULT_PORT. Since that's so confusing, here are a few
33
- # examples:
28
+ # Create a Mongo database server instance. You specify either one or a
29
+ # pair of servers. If one, you also say if connecting to a slave is
30
+ # OK. In either case, the host default is "localhost" and port default
31
+ # is DEFAULT_PORT.
34
32
  #
35
- # Mongo.new # localhost, DEFAULT_PORT
36
- # Mongo.new("localhost") # localhost, DEFAULT_PORT
37
- # Mongo.new("localhost", 3000) # localhost, 3000
38
- # Mongo.new([["localhost"]]) # localhost, DEFAULT_PORT
39
- # Mongo.new([["localhost", 3000]]) # localhost, 3000
40
- # Mongo.new([["db1.example.com", 3000], ["db2.example.com", 3000]]])
33
+ # If you specify a pair, pair_or_host is a hash with two keys :left
34
+ # and :right. Each key maps to either
35
+ # * a server name, in which case port is DEFAULT_PORT
36
+ # * a port number, in which case server is "localhost"
37
+ # * an array containing a server name and a port number in either order
41
38
  #
42
- # When a DB object first connects, it tries nodes and stops at the
43
- # first one it connects to.
44
- def initialize(nodes_or_host=nil, port=nil)
45
- @nodes = case nodes_or_host
39
+ # +options+ are passed on to each DB instance:
40
+ #
41
+ # :slave_ok :: Only used if one host is specified. If false, when
42
+ # connecting to that host/port a DB object will check to
43
+ # see if the server is the master. If it is not, an error
44
+ # is thrown.
45
+ #
46
+ # :auto_reconnect :: If a DB connection gets closed (for example, we
47
+ # have a server pair and saw the "not master"
48
+ # error, which closes the connection), then
49
+ # automatically try to reconnect to the master or
50
+ # to the single server we have been given. Defaults
51
+ # to +false+.
52
+ #
53
+ # Since that's so confusing, here are a few examples:
54
+ #
55
+ # Mongo.new # localhost, DEFAULT_PORT, !slave
56
+ # Mongo.new("localhost") # localhost, DEFAULT_PORT, !slave
57
+ # Mongo.new("localhost", 3000) # localhost, 3000, slave not ok
58
+ # # localhost, 3000, slave ok
59
+ # Mongo.new("localhost", 3000, :slave_ok => true)
60
+ # # localhost, DEFAULT_PORT, auto reconnect
61
+ # Mongo.new(nil, nil, :auto_reconnect => true)
62
+ #
63
+ # # A pair of servers. DB will always talk to the master. On socket
64
+ # # error or "not master" error, we will auto-reconnect to the
65
+ # # current master.
66
+ # Mongo.new({:left => ["db1.example.com", 3000],
67
+ # :right => "db2.example.com"}, # DEFAULT_PORT
68
+ # nil, :auto_reconnect => true)
69
+ #
70
+ # # Here, :right is localhost/DEFAULT_PORT. No auto-reconnect.
71
+ # Mongo.new({:left => ["db1.example.com", 3000]})
72
+ #
73
+ # When a DB object first connects to a pair, it will find the master
74
+ # instance and connect to that one.
75
+ def initialize(pair_or_host=nil, port=nil, options={})
76
+ @pair = case pair_or_host
46
77
  when String
47
- [[nodes_or_host, port || DEFAULT_PORT]]
48
- when Array
49
- nodes_or_host.collect { |nh| [nh[0], nh[1] || DEFAULT_PORT] }
78
+ [[pair_or_host, port || DEFAULT_PORT]]
79
+ when Hash
80
+ connections = []
81
+ connections << pair_val_to_connection(pair_or_host[:left])
82
+ connections << pair_val_to_connection(pair_or_host[:right])
83
+ connections
50
84
  when nil
51
85
  [['localhost', DEFAULT_PORT]]
52
86
  end
87
+ @options = options
53
88
  end
54
89
 
55
- # Return the XGen::Mongo::Driver::DB named +db_name+. See DB#new for
56
- # +options+.
90
+ # Return the XGen::Mongo::Driver::DB named +db_name+. The slave_ok and
91
+ # auto_reconnect options passed in via #new may be overridden here.
92
+ # See DB#new for other options you can pass in.
57
93
  def db(db_name, options={})
58
- XGen::Mongo::Driver::DB.new(db_name, @nodes, options)
94
+ XGen::Mongo::Driver::DB.new(db_name, @pair, @options.merge(options))
59
95
  end
60
96
 
61
97
  # Returns a hash containing database names as keys and disk space for
@@ -91,6 +127,26 @@ module XGen
91
127
 
92
128
  protected
93
129
 
130
+ # Turns an array containing an optional host name string and an
131
+ # optional port number integer into a [host, port] pair array.
132
+ def pair_val_to_connection(a)
133
+ case a
134
+ when nil
135
+ ['localhost', DEFAULT_PORT]
136
+ when String
137
+ [a, DEFAULT_PORT]
138
+ when Integer
139
+ ['localhost', a]
140
+ when Array
141
+ connection = ['localhost', DEFAULT_PORT]
142
+ connection[0] = a[0] if a[0].kind_of?(String)
143
+ connection[0] = a[1] if a[1].kind_of?(String)
144
+ connection[1] = a[0] if a[0].kind_of?(Integer)
145
+ connection[1] = a[1] if a[1].kind_of?(Integer)
146
+ connection
147
+ end
148
+ end
149
+
94
150
  # Send cmd (a hash, possibly ordered) to the admin database and return
95
151
  # the answer. Raises an error unless the return is "ok" (DB#ok?
96
152
  # returns +true+).
@@ -1,6 +1,6 @@
1
1
  Gem::Specification.new do |s|
2
2
  s.name = 'mongo'
3
- s.version = '0.2.4'
3
+ s.version = '0.2.5'
4
4
  s.platform = Gem::Platform::RUBY
5
5
  s.summary = 'Simple pure-Ruby driver for the 10gen Mongo DB'
6
6
  s.description = 'A pure-Ruby driver for the 10gen Mongo DB. For more information about Mongo, see http://www.mongodb.org.'
data/tests/test_db.rb CHANGED
@@ -28,7 +28,7 @@ class DBTest < Test::Unit::TestCase
28
28
  end
29
29
 
30
30
  def teardown
31
- if @db.connected?
31
+ if @db && @db.connected?
32
32
  @users.clear if @users
33
33
  @db.close
34
34
  end
@@ -50,17 +50,10 @@ class DBTest < Test::Unit::TestCase
50
50
  assert_equal 'ruby-mongo-test.test', @db.full_coll_name(coll.name)
51
51
  end
52
52
 
53
- def test_master
54
- # Doesn't really test anything since we probably only have one database
55
- # during this test.
56
- @db.switch_to_master
57
- assert @db.connected?
58
- end
59
-
60
- def test_array
53
+ def test_pair
61
54
  @db.close
62
55
  @users = nil
63
- @db = Mongo.new([["nosuch.example.com"], [@host, @port]]).db('ruby-mongo-test')
56
+ @db = Mongo.new({:left => "nosuch.example.com", :right => [@host, @port]}).db('ruby-mongo-test')
64
57
  assert @db.connected?
65
58
  end
66
59
 
@@ -101,4 +94,28 @@ class DBTest < Test::Unit::TestCase
101
94
  @db.logout # only testing that we don't throw exception
102
95
  end
103
96
 
97
+ def test_auto_connect
98
+ @db.close
99
+ db = Mongo.new(@host, @port, :auto_reconnect => true).db('ruby-mongo-test')
100
+ assert db.connected?
101
+ assert db.auto_reconnect?
102
+ db.close
103
+ assert !db.connected?
104
+ assert db.auto_reconnect?
105
+ db.collection('test').insert('a' => 1)
106
+ assert db.connected?
107
+ end
108
+
109
+ def test_error
110
+ doc = @db.send(:db_command, :forceerror => 1)
111
+ assert @db.error?
112
+ err = @db.error
113
+ assert_match /forced error/, err
114
+
115
+ # ask again
116
+ assert @db.error?
117
+ err2 = @db.error
118
+ assert_equal err, err2
119
+ end
120
+
104
121
  end
data/tests/test_db_api.rb CHANGED
@@ -47,6 +47,17 @@ class DBAPITest < Test::Unit::TestCase
47
47
  assert docs.detect { |row| row['b'] == 4 }
48
48
  end
49
49
 
50
+ def test_insert_multiple
51
+ @coll.insert({'a' => 2}, {'b' => 3})
52
+
53
+ assert_equal 3, @coll.count
54
+ docs = @coll.find().to_a
55
+ assert_equal 3, docs.length
56
+ assert docs.detect { |row| row['a'] == 1 }
57
+ assert docs.detect { |row| row['a'] == 2 }
58
+ assert docs.detect { |row| row['b'] == 3 }
59
+ end
60
+
50
61
  def test_find_simple
51
62
  @r2 = @coll.insert('a' => 2)
52
63
  @r3 = @coll.insert('b' => 3)
data/tests/test_mongo.rb CHANGED
@@ -41,4 +41,24 @@ class MongoTest < Test::Unit::TestCase
41
41
  assert !@mongo.database_names.include?('will-be-deleted')
42
42
  end
43
43
 
44
+ def test_pair
45
+ db = Mongo.new({:left => ['foo', 123]})
46
+ pair = db.instance_variable_get('@pair')
47
+ assert_equal 2, pair.length
48
+ assert_equal ['foo', 123], pair[0]
49
+ assert_equal ['localhost', Mongo::DEFAULT_PORT], pair[1]
50
+
51
+ db = Mongo.new({:right => 'bar'})
52
+ pair = db.instance_variable_get('@pair')
53
+ assert_equal 2, pair.length
54
+ assert_equal ['localhost', Mongo::DEFAULT_PORT], pair[0]
55
+ assert_equal ['bar', Mongo::DEFAULT_PORT], pair[1]
56
+
57
+ db = Mongo.new({:right => [123, 'foo'], :left => 'bar'})
58
+ pair = db.instance_variable_get('@pair')
59
+ assert_equal 2, pair.length
60
+ assert_equal ['bar', Mongo::DEFAULT_PORT], pair[0]
61
+ assert_equal ['foo', 123], pair[1]
62
+ end
63
+
44
64
  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.2.4
4
+ version: 0.2.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jim Menard