mongo 0.0.4 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +8 -12
- data/Rakefile +1 -1
- data/lib/mongo/collection.rb +12 -4
- data/lib/mongo/cursor.rb +26 -23
- data/lib/mongo/db.rb +51 -11
- data/lib/mongo/message/query_message.rb +23 -13
- data/lib/mongo/mongo.rb +26 -4
- data/lib/mongo/query.rb +8 -1
- data/tests/test_admin.rb +3 -2
- data/tests/test_bson.rb +0 -1
- data/tests/test_cursor.rb +4 -1
- data/tests/test_db.rb +51 -0
- data/tests/test_db_api.rb +4 -16
- data/tests/test_ordered_hash.rb +0 -1
- metadata +3 -2
data/README.rdoc
CHANGED
@@ -111,11 +111,6 @@ See the git log comments.
|
|
111
111
|
current thinking is that Mongo will allow a subset of JavaScript (which we
|
112
112
|
would have to send as a string), but this is still under discussion.
|
113
113
|
|
114
|
-
* Add explain and hint support.
|
115
|
-
|
116
|
-
* Only update message sizes once, not after every write of a value. This will
|
117
|
-
require an explicit call to update_message_length in each message subclass.
|
118
|
-
|
119
114
|
* Tests for update and repsert.
|
120
115
|
|
121
116
|
* Add a way to specify a collection of databases on startup (a simple array of
|
@@ -123,17 +118,18 @@ See the git log comments.
|
|
123
118
|
then find the master and, on each subsequent command, ask that machine if it
|
124
119
|
is the master before proceeding.
|
125
120
|
|
126
|
-
* Tests that prove that this driver's ObjectID and Geir's Java version do the
|
127
|
-
same thing. (I've done so manually.)
|
128
|
-
|
129
|
-
* Support more types: REF, SYMBOL, CODE_W_SCOPE, etc.
|
130
|
-
|
131
121
|
* Introduce optional per-database and per-collection PKInjector.
|
132
122
|
|
133
123
|
* More tests.
|
134
124
|
|
135
|
-
|
136
|
-
|
125
|
+
== Optimizations
|
126
|
+
|
127
|
+
* Only update message sizes once, not after every write of a value. This will
|
128
|
+
require an explicit call to update_message_length in each message subclass.
|
129
|
+
|
130
|
+
* ensure_index commands should be cached to prevent excessive communication
|
131
|
+
with the database. (Or, the driver user should be informed that ensure_index
|
132
|
+
is not a lightweight operation for the particular driver.)
|
137
133
|
|
138
134
|
|
139
135
|
= Credits
|
data/Rakefile
CHANGED
@@ -7,7 +7,7 @@ require 'rake/gempackagetask'
|
|
7
7
|
require 'rake/contrib/rubyforgepublisher'
|
8
8
|
|
9
9
|
GEM = "mongo"
|
10
|
-
GEM_VERSION = '0.0
|
10
|
+
GEM_VERSION = '0.1.0'
|
11
11
|
SUMMARY = 'Simple pure-Ruby driver for the 10gen Mongo DB'
|
12
12
|
DESCRIPTION = 'This is a simple pure-Ruby driver for the 10gen Mongo DB. For more information about Mongo, see http://www.mongodb.org.'
|
13
13
|
AUTHOR = 'Jim Menard'
|
data/lib/mongo/collection.rb
CHANGED
@@ -31,11 +31,19 @@ module XGen
|
|
31
31
|
end
|
32
32
|
|
33
33
|
# Set hint fields to use and return +self+. hint_fields may be a
|
34
|
-
# single field name
|
35
|
-
#
|
36
|
-
# exist.
|
34
|
+
# single field name, array of field names, or a hash whose keys will
|
35
|
+
# become the hint field names. May be +nil+.
|
37
36
|
def hint(hint_fields)
|
38
|
-
@
|
37
|
+
@hint_fields = case hint_fields
|
38
|
+
when String
|
39
|
+
[hint_fields]
|
40
|
+
when Hash
|
41
|
+
hint_fields.keys
|
42
|
+
when nil
|
43
|
+
nil
|
44
|
+
else
|
45
|
+
hint_fields.to_a
|
46
|
+
end
|
39
47
|
self
|
40
48
|
end
|
41
49
|
|
data/lib/mongo/cursor.rb
CHANGED
@@ -43,11 +43,20 @@ module XGen
|
|
43
43
|
def closed?; @closed; end
|
44
44
|
|
45
45
|
# Set hint fields to use and return +self+. hint_fields may be a
|
46
|
-
# single field name
|
47
|
-
#
|
48
|
-
# exist.
|
46
|
+
# single field name, array of field names, or a hash whose keys will
|
47
|
+
# become the hint field names. May be +nil+. If no hint fields are
|
48
|
+
# specified, the ones in the collection are used if they exist.
|
49
49
|
def hint(hint_fields)
|
50
|
-
@hint_fields = hint_fields
|
50
|
+
@hint_fields = case hint_fields
|
51
|
+
when String
|
52
|
+
[hint_fields]
|
53
|
+
when Hash
|
54
|
+
hint_fields.keys
|
55
|
+
when nil
|
56
|
+
nil
|
57
|
+
else
|
58
|
+
hint_fields.to_a
|
59
|
+
end
|
51
60
|
self
|
52
61
|
end
|
53
62
|
|
@@ -61,7 +70,7 @@ module XGen
|
|
61
70
|
def next_object
|
62
71
|
refill_via_get_more if num_remaining == 0
|
63
72
|
o = @cache.shift
|
64
|
-
raise o['$err'] if o['$err']
|
73
|
+
raise o['$err'] if o && o['$err']
|
65
74
|
o
|
66
75
|
end
|
67
76
|
|
@@ -116,13 +125,15 @@ module XGen
|
|
116
125
|
|
117
126
|
# Returns an explain plan record.
|
118
127
|
def explain
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
c = Cursor.new(@db, @collection,
|
123
|
-
|
128
|
+
old_val = @query.explain
|
129
|
+
@query.explain = true
|
130
|
+
|
131
|
+
c = Cursor.new(@db, @collection, @query)
|
132
|
+
explanation = c.next_object
|
124
133
|
c.close
|
125
|
-
|
134
|
+
|
135
|
+
@query.explain = old_val
|
136
|
+
explanation
|
126
137
|
end
|
127
138
|
|
128
139
|
# Close the cursor.
|
@@ -201,19 +212,11 @@ module XGen
|
|
201
212
|
# Run query first time we request an object from the wire
|
202
213
|
unless @query_run
|
203
214
|
hints = @hint_fields || @collection.hint_fields
|
204
|
-
|
205
|
-
query =
|
206
|
-
|
207
|
-
hints.each { |field| h[field] = 1 }
|
208
|
-
sel = OrderedHash.new
|
209
|
-
sel['query'] = @query.selector
|
210
|
-
sel['$hint'] = h
|
211
|
-
Query.new(sel)
|
212
|
-
else
|
213
|
-
@query
|
214
|
-
end
|
215
|
-
@db.send_query_message(QueryMessage.new(@db.name, @collection.name, query))
|
215
|
+
old_hints = @query.hint_fields
|
216
|
+
@query.hint_fields = hints
|
217
|
+
@db.send_query_message(QueryMessage.new(@db.name, @collection.name, @query))
|
216
218
|
@query_run = true
|
219
|
+
@query.hint_fields = old_hints
|
217
220
|
read_all
|
218
221
|
end
|
219
222
|
end
|
data/lib/mongo/db.rb
CHANGED
@@ -16,7 +16,6 @@
|
|
16
16
|
|
17
17
|
require 'socket'
|
18
18
|
require 'mutex_m'
|
19
|
-
require 'mongo/mongo'
|
20
19
|
require 'mongo/collection'
|
21
20
|
require 'mongo/message'
|
22
21
|
require 'mongo/query'
|
@@ -49,25 +48,47 @@ module XGen
|
|
49
48
|
# The name of the database.
|
50
49
|
attr_reader :name
|
51
50
|
|
52
|
-
|
51
|
+
# Host to which we are currently connected.
|
52
|
+
attr_reader :host
|
53
|
+
# Port to which we are currently connected.
|
54
|
+
attr_reader :port
|
53
55
|
|
54
|
-
#
|
56
|
+
# An array of [host, port] pairs.
|
57
|
+
attr_reader :nodes
|
58
|
+
|
59
|
+
# The database's socket. For internal (and Cursor) use only.
|
55
60
|
attr_reader :socket
|
56
61
|
|
57
62
|
# db_name :: The database name
|
58
63
|
#
|
59
|
-
#
|
60
|
-
#
|
61
|
-
# port :: The database port number. Defaults to
|
62
|
-
# XGen::Mongo::Driver::Mongo::DEFAULT_PORT.
|
64
|
+
# nodes :: An array of [host, port] pairs.
|
63
65
|
#
|
64
|
-
|
66
|
+
# When a DB object first connects, it tries the first node. If that
|
67
|
+
# fails, it keeps trying to connect to the remaining nodes until it
|
68
|
+
# sucessfully connects.
|
69
|
+
def initialize(db_name, nodes)
|
65
70
|
raise "Invalid DB name" if !db_name || (db_name && db_name.length > 0 && db_name.include?("."))
|
66
|
-
@name, @
|
67
|
-
@socket = TCPSocket.new(@host, @port)
|
71
|
+
@name, @nodes = db_name, nodes
|
68
72
|
@strict = false
|
69
73
|
@semaphore = Object.new
|
70
74
|
@semaphore.extend Mutex_m
|
75
|
+
connect_to_first_available_host
|
76
|
+
end
|
77
|
+
|
78
|
+
def connect_to_first_available_host
|
79
|
+
close if @socket
|
80
|
+
@host = @port = nil
|
81
|
+
@nodes.detect { |hp|
|
82
|
+
@host, @port = *hp
|
83
|
+
begin
|
84
|
+
@socket = TCPSocket.new(@host, @port)
|
85
|
+
break if ok?(db_command(:ismaster => 1)) # success
|
86
|
+
rescue => ex
|
87
|
+
close if @socket
|
88
|
+
end
|
89
|
+
@socket
|
90
|
+
}
|
91
|
+
raise "error: failed to connect to any given host:port" unless @socket
|
71
92
|
end
|
72
93
|
|
73
94
|
# Returns an array of collection names. Each name is of the form
|
@@ -167,9 +188,28 @@ module XGen
|
|
167
188
|
end
|
168
189
|
end
|
169
190
|
|
191
|
+
# Switches our socket to the master database. If we are already the
|
192
|
+
# master, no change is made.
|
193
|
+
def switch_to_master
|
194
|
+
master_str = master()
|
195
|
+
unless master_str == "#@host:#@port"
|
196
|
+
@semaphore.synchronize {
|
197
|
+
master_str =~ /(.+):(\d+)/
|
198
|
+
@host, @port = $1, $2
|
199
|
+
close()
|
200
|
+
@socket = TCPSocket.new(@host, @port)
|
201
|
+
}
|
202
|
+
end
|
203
|
+
end
|
204
|
+
|
170
205
|
# Close the connection to the database.
|
171
206
|
def close
|
172
|
-
@socket.close
|
207
|
+
@socket.close if @socket
|
208
|
+
@socket = nil
|
209
|
+
end
|
210
|
+
|
211
|
+
def connected?
|
212
|
+
@socket != nil
|
173
213
|
end
|
174
214
|
|
175
215
|
# Send a MsgMessage to the database.
|
@@ -18,21 +18,31 @@ module XGen
|
|
18
18
|
write_int(query.number_to_skip)
|
19
19
|
write_int(query.number_to_return)
|
20
20
|
sel = query.selector
|
21
|
-
if query.
|
21
|
+
if query.contains_special_fields
|
22
22
|
sel = OrderedHash.new
|
23
23
|
sel['query'] = query.selector
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
24
|
+
if query.order_by && query.order_by.length > 0
|
25
|
+
sel['orderby'] = case query.order_by
|
26
|
+
when String
|
27
|
+
{query.order_by => 1}
|
28
|
+
when Array
|
29
|
+
h = OrderedHash.new
|
30
|
+
query.order_by.each { |ob| h[ob] = 1 }
|
31
|
+
h
|
32
|
+
when Hash # Should be an ordered hash, but this message doesn't care
|
33
|
+
query.order_by
|
34
|
+
else
|
35
|
+
raise "illegal order_by: is a #{query.order_by.class.name}, must be String, Array, Hash, or OrderedHash"
|
36
|
+
end
|
37
|
+
end
|
38
|
+
if query.hint_fields && query.hint_fields.length > 0
|
39
|
+
hints = OrderedHash.new
|
40
|
+
query.hint_fields.each { |hf| hints[hf] = 1 }
|
41
|
+
sel['$hint'] = hints
|
42
|
+
end
|
43
|
+
if query.explain
|
44
|
+
sel['$explain'] = true
|
45
|
+
end
|
36
46
|
|
37
47
|
end
|
38
48
|
write_doc(sel)
|
data/lib/mongo/mongo.rb
CHANGED
@@ -25,14 +25,36 @@ module XGen
|
|
25
25
|
|
26
26
|
DEFAULT_PORT = 27017
|
27
27
|
|
28
|
-
#
|
29
|
-
|
30
|
-
|
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:
|
34
|
+
#
|
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]]])
|
41
|
+
#
|
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
|
46
|
+
when String
|
47
|
+
[[nodes_or_host, port || DEFAULT_PORT]]
|
48
|
+
when Array
|
49
|
+
nodes_or_host.collect { |nh| [nh[0], nh[1] || DEFAULT_PORT] }
|
50
|
+
when nil
|
51
|
+
[['localhost', DEFAULT_PORT]]
|
52
|
+
end
|
31
53
|
end
|
32
54
|
|
33
55
|
# Return the XGen::Mongo::Driver::DB named +db_name+.
|
34
56
|
def db(db_name)
|
35
|
-
XGen::Mongo::Driver::DB.new(db_name, @
|
57
|
+
XGen::Mongo::Driver::DB.new(db_name, @nodes)
|
36
58
|
end
|
37
59
|
|
38
60
|
# Not implemented.
|
data/lib/mongo/query.rb
CHANGED
@@ -14,7 +14,6 @@
|
|
14
14
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
15
15
|
# ++
|
16
16
|
|
17
|
-
require 'socket'
|
18
17
|
require 'mongo/collection'
|
19
18
|
require 'mongo/message'
|
20
19
|
|
@@ -27,6 +26,10 @@ module XGen
|
|
27
26
|
class Query
|
28
27
|
|
29
28
|
attr_accessor :number_to_skip, :number_to_return, :order_by
|
29
|
+
# If true, $explain will be set in QueryMessage that uses this query.
|
30
|
+
attr_accessor :explain
|
31
|
+
# Either +nil+ or an array of hint field names.
|
32
|
+
attr_accessor :hint_fields
|
30
33
|
attr_reader :selector # writer defined below
|
31
34
|
|
32
35
|
# sel :: A hash describing the query. See the Mongo docs for details.
|
@@ -97,6 +100,10 @@ module XGen
|
|
97
100
|
nil
|
98
101
|
end
|
99
102
|
end
|
103
|
+
|
104
|
+
def contains_special_fields
|
105
|
+
(@order_by != nil && @order_by.length > 0) || @explain || @hint_fields
|
106
|
+
end
|
100
107
|
end
|
101
108
|
end
|
102
109
|
end
|
data/tests/test_admin.rb
CHANGED
data/tests/test_bson.rb
CHANGED
data/tests/test_cursor.rb
CHANGED
data/tests/test_db.rb
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
$LOAD_PATH[0,0] = File.join(File.dirname(__FILE__), '..', 'lib')
|
2
|
+
require 'mongo'
|
3
|
+
require 'test/unit'
|
4
|
+
|
5
|
+
# NOTE: assumes Mongo is running
|
6
|
+
class DBAPITest < Test::Unit::TestCase
|
7
|
+
|
8
|
+
include XGen::Mongo::Driver
|
9
|
+
|
10
|
+
def setup
|
11
|
+
@host = ENV['MONGO_RUBY_DRIVER_HOST'] || 'localhost'
|
12
|
+
@port = ENV['MONGO_RUBY_DRIVER_PORT'] || Mongo::DEFAULT_PORT
|
13
|
+
@db = Mongo.new(@host, @port).db('ruby-mongo-test')
|
14
|
+
end
|
15
|
+
|
16
|
+
def teardown
|
17
|
+
if @db.connected?
|
18
|
+
@db.close
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def test_close
|
23
|
+
@db.close
|
24
|
+
assert !@db.connected?
|
25
|
+
begin
|
26
|
+
@db.collection('test').insert('a' => 1)
|
27
|
+
fail "expected 'NilClass' exception"
|
28
|
+
rescue => ex
|
29
|
+
assert_match /NilClass/, ex.to_s
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def test_full_coll_name
|
34
|
+
coll = @db.collection('test')
|
35
|
+
assert_equal 'ruby-mongo-test.test', @db.full_coll_name(coll.name)
|
36
|
+
end
|
37
|
+
|
38
|
+
def test_master
|
39
|
+
# Doesn't really test anything since we probably only have one database
|
40
|
+
# during this test.
|
41
|
+
@db.switch_to_master
|
42
|
+
assert @db.connected?
|
43
|
+
end
|
44
|
+
|
45
|
+
def test_array
|
46
|
+
@db.close
|
47
|
+
@db = Mongo.new([["nosuch.example.com"], [@host, @port]]).db('ruby-mongo-test')
|
48
|
+
assert @db.connected?
|
49
|
+
end
|
50
|
+
|
51
|
+
end
|
data/tests/test_db_api.rb
CHANGED
@@ -18,7 +18,10 @@ class DBAPITest < Test::Unit::TestCase
|
|
18
18
|
end
|
19
19
|
|
20
20
|
def teardown
|
21
|
-
|
21
|
+
if @db.connected?
|
22
|
+
@coll.clear unless @coll == nil
|
23
|
+
@db.close
|
24
|
+
end
|
22
25
|
end
|
23
26
|
|
24
27
|
def test_clear
|
@@ -196,17 +199,6 @@ class DBAPITest < Test::Unit::TestCase
|
|
196
199
|
assert_equal 4, docs.size
|
197
200
|
end
|
198
201
|
|
199
|
-
def test_close
|
200
|
-
@db.close
|
201
|
-
assert @db.socket.closed?
|
202
|
-
begin
|
203
|
-
@coll.insert('a' => 1)
|
204
|
-
fail "expected IOError exception"
|
205
|
-
rescue IOError => ex
|
206
|
-
assert_match /closed stream/, ex.to_s
|
207
|
-
end
|
208
|
-
end
|
209
|
-
|
210
202
|
def test_drop_collection
|
211
203
|
assert @db.drop_collection(@coll.name), "drop of collection #{@coll.name} failed"
|
212
204
|
assert !@db.collection_names.include?(@coll_full_name)
|
@@ -253,10 +245,6 @@ class DBAPITest < Test::Unit::TestCase
|
|
253
245
|
end
|
254
246
|
end
|
255
247
|
|
256
|
-
def test_full_coll_name
|
257
|
-
assert_equal @coll_full_name, @db.full_coll_name(@coll.name)
|
258
|
-
end
|
259
|
-
|
260
248
|
def test_index_information
|
261
249
|
@db.create_index(@coll.name, 'index_name', ['a'])
|
262
250
|
list = @db.index_information(@coll.name)
|
data/tests/test_ordered_hash.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: mongo
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0
|
4
|
+
version: 0.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jim Menard
|
@@ -9,7 +9,7 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date: 2009-01-
|
12
|
+
date: 2009-01-15 00:00:00 -05:00
|
13
13
|
default_executable:
|
14
14
|
dependencies: []
|
15
15
|
|
@@ -55,6 +55,7 @@ files:
|
|
55
55
|
- tests/test_bson.rb
|
56
56
|
- tests/test_byte_buffer.rb
|
57
57
|
- tests/test_cursor.rb
|
58
|
+
- tests/test_db.rb
|
58
59
|
- tests/test_db_api.rb
|
59
60
|
- tests/test_db_connection.rb
|
60
61
|
- tests/test_message.rb
|