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 +5 -0
- data/lib/mongo/cursor.rb +13 -1
- data/lib/mongo/db.rb +64 -22
- data/lib/mongo/mongo.rb +78 -22
- data/mongo-ruby-driver.gemspec +1 -1
- data/tests/test_db.rb +27 -10
- data/tests/test_db_api.rb +11 -0
- data/tests/test_mongo.rb +20 -0
- metadata +1 -1
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
|
-
|
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
|
-
#
|
96
|
-
#
|
97
|
-
#
|
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
|
-
|
125
|
+
connect_to_master
|
106
126
|
end
|
107
127
|
|
108
|
-
def
|
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
|
-
|
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
|
-
@
|
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
|
-
#
|
29
|
-
#
|
30
|
-
#
|
31
|
-
#
|
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
|
-
#
|
36
|
-
#
|
37
|
-
#
|
38
|
-
#
|
39
|
-
#
|
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
|
-
#
|
43
|
-
#
|
44
|
-
|
45
|
-
|
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
|
-
[[
|
48
|
-
when
|
49
|
-
|
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+.
|
56
|
-
#
|
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, @
|
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+).
|
data/mongo-ruby-driver.gemspec
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
Gem::Specification.new do |s|
|
2
2
|
s.name = 'mongo'
|
3
|
-
s.version = '0.2.
|
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
|
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(
|
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
|