mongo 0.0.4 → 0.1.0
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 +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
|