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 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
- * Study src/main/ed/db/{dbcollection,dbcursor,db}.js and ByteEncoder.java in
136
- the Babble code. That's what I should be writing to.
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.4'
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'
@@ -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 or array of field names. May be +nil+. If no hint
35
- # fields are specified, the ones in the collection are used if they
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
- @hint_fileds = hint_fileds
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 or array of field names. May be +nil+. If no hint
47
- # fields are specified, the ones in the collection are used if they
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
- sel = OrderedHash.new
120
- sel['query'] = @query.selector
121
- sel['$explain'] = true
122
- c = Cursor.new(@db, @collection, Query.new(sel))
123
- e = c.next_object
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
- e
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
- hints = [hints] if hints.kind_of?(String)
205
- query = if hints
206
- h = {}
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
- attr_reader :host, :port
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
- # The database's socket. For internal use only.
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
- # host :: The database host name or IP address. Defaults to 'localhost'.
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
- def initialize(db_name, host='localhost', port=XGen::Mongo::Driver::Mongo::DEFAULT_PORT)
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, @host, @port = db_name, host, port
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.order_by && query.order_by.length > 0
21
+ if query.contains_special_fields
22
22
  sel = OrderedHash.new
23
23
  sel['query'] = query.selector
24
- sel['orderby'] = case query.order_by
25
- when String
26
- {query.order_by => 1}
27
- when Array
28
- h = OrderedHash.new
29
- query.order_by.each { |ob| h[ob] = 1 }
30
- h
31
- when Hash # Should be an ordered hash, but this message doesn't care
32
- query.order_by
33
- else
34
- raise "illegal order_by: is a #{query.order_by.class.name}, must be String, Array, Hash, or OrderedHash"
35
- end
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
- # Host default is 'localhost', port default is DEFAULT_PORT.
29
- def initialize(host='localhost', port=DEFAULT_PORT)
30
- @host, @port = host, port
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, @host, @port)
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
@@ -20,9 +20,10 @@ class AdminTest < Test::Unit::TestCase
20
20
  end
21
21
 
22
22
  def teardown
23
- unless @db.socket.closed?
23
+ if @db.connected?
24
24
  @admin.profiling_level = :off
25
- @coll.clear unless @coll == nil
25
+ @coll.clear if @coll
26
+ @db.close
26
27
  end
27
28
  end
28
29
 
data/tests/test_bson.rb CHANGED
@@ -2,7 +2,6 @@ $LOAD_PATH[0,0] = File.join(File.dirname(__FILE__), '..', 'lib')
2
2
  require 'mongo'
3
3
  require 'test/unit'
4
4
 
5
- # NOTE: assumes Mongo is running
6
5
  class BSONTest < Test::Unit::TestCase
7
6
 
8
7
  include XGen::Mongo::Driver
data/tests/test_cursor.rb CHANGED
@@ -18,7 +18,10 @@ class CursorTest < Test::Unit::TestCase
18
18
  end
19
19
 
20
20
  def teardown
21
- @coll.clear unless @coll == nil || @db.socket.closed?
21
+ if @db.connected?
22
+ @coll.clear if @coll
23
+ @db.close
24
+ end
22
25
  end
23
26
 
24
27
  def test_explain
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
- @coll.clear unless @coll == nil || @db.socket.closed?
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)
@@ -2,7 +2,6 @@ $LOAD_PATH[0,0] = File.join(File.dirname(__FILE__), '..', 'lib')
2
2
  require 'mongo/util/ordered_hash'
3
3
  require 'test/unit'
4
4
 
5
- # NOTE: assumes Mongo is running
6
5
  class OrderedHashTest < Test::Unit::TestCase
7
6
 
8
7
  def setup
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
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-14 00:00:00 -05:00
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