mongodb-mongo 0.2.4 → 0.2.5

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
@@ -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