mongo 1.1.1 → 1.1.2

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.
@@ -42,6 +42,9 @@ module Mongo
42
42
  # Raised on failures in connection to the database server.
43
43
  class ConnectionError < MongoRubyError; end
44
44
 
45
+ # Raised on failures in connection to the database server.
46
+ class ReplicaSetConnectionError < ConnectionError; end
47
+
45
48
  # Raised on failures in connection to the database server.
46
49
  class ConnectionTimeoutError < MongoRubyError; end
47
50
 
@@ -38,7 +38,10 @@ module Mongo
38
38
  @chunks = @db["#{fs_name}.chunks"]
39
39
  @fs_name = fs_name
40
40
 
41
- @chunks.create_index([['files_id', Mongo::ASCENDING], ['n', Mongo::ASCENDING]], :unique => true)
41
+ # Ensure indexes only if not connected to slave.
42
+ unless db.connection.slave_ok?
43
+ @chunks.create_index([['files_id', Mongo::ASCENDING], ['n', Mongo::ASCENDING]], :unique => true)
44
+ end
42
45
  end
43
46
 
44
47
  # Store a file in the file store. This method is designed only for writing new files;
@@ -39,8 +39,11 @@ module Mongo
39
39
 
40
40
  @default_query_opts = {:sort => [['filename', 1], ['uploadDate', -1]], :limit => 1}
41
41
 
42
- @files.create_index([['filename', 1], ['uploadDate', -1]])
43
- @chunks.create_index([['files_id', Mongo::ASCENDING], ['n', Mongo::ASCENDING]], :unique => true)
42
+ # Ensure indexes only if not connected to slave.
43
+ unless db.connection.slave_ok?
44
+ @files.create_index([['filename', 1], ['uploadDate', -1]])
45
+ @chunks.create_index([['files_id', Mongo::ASCENDING], ['n', Mongo::ASCENDING]], :unique => true)
46
+ end
44
47
  end
45
48
 
46
49
  # Open a file for reading or writing. Note that the options for this method only apply
@@ -0,0 +1,115 @@
1
+ # encoding: UTF-8
2
+
3
+ # --
4
+ # Copyright (C) 2008-2010 10gen Inc.
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License");
7
+ # you may not use this file except in compliance with the License.
8
+ # You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS,
14
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ # See the License for the specific language governing permissions and
16
+ # limitations under the License.
17
+
18
+ module Mongo
19
+ class Pool
20
+
21
+ def initialize(host, port, socket, options={})
22
+ @host, @port = host, port
23
+
24
+ # Pool size and timeout.
25
+ @size = options[:pool_size] || 1
26
+ @timeout = options[:timeout] || 5.0
27
+
28
+ # Mutex for synchronizing pool access
29
+ @connection_mutex = Mutex.new
30
+
31
+ # Global safe option. This is false by default.
32
+ @safe = options[:safe] || false
33
+
34
+ # Create a mutex when a new key, in this case a socket,
35
+ # is added to the hash.
36
+ @safe_mutexes = Hash.new { |h, k| h[k] = Mutex.new }
37
+
38
+ # Condition variable for signal and wait
39
+ @queue = ConditionVariable.new
40
+
41
+ @sockets = []
42
+ @checked_out = []
43
+ end
44
+
45
+ def close
46
+ end
47
+
48
+ # Return a socket to the pool.
49
+ def checkin(socket)
50
+ @connection_mutex.synchronize do
51
+ @checked_out.delete(socket)
52
+ @queue.signal
53
+ end
54
+ true
55
+ end
56
+
57
+ # Adds a new socket to the pool and checks it out.
58
+ #
59
+ # This method is called exclusively from #checkout;
60
+ # therefore, it runs within a mutex.
61
+ def checkout_new_socket
62
+ begin
63
+ socket = TCPSocket.new(@host, @port)
64
+ socket.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
65
+ rescue => ex
66
+ raise ConnectionFailure, "Failed to connect socket: #{ex}"
67
+ end
68
+ @sockets << socket
69
+ @checked_out << socket
70
+ socket
71
+ end
72
+
73
+ # Checks out the first available socket from the pool.
74
+ #
75
+ # This method is called exclusively from #checkout;
76
+ # therefore, it runs within a mutex.
77
+ def checkout_existing_socket
78
+ socket = (@sockets - @checked_out).first
79
+ @checked_out << socket
80
+ socket
81
+ end
82
+
83
+ # Check out an existing socket or create a new socket if the maximum
84
+ # pool size has not been exceeded. Otherwise, wait for the next
85
+ # available socket.
86
+ def checkout
87
+ connect if !connected?
88
+ start_time = Time.now
89
+ loop do
90
+ if (Time.now - start_time) > @timeout
91
+ raise ConnectionTimeoutError, "could not obtain connection within " +
92
+ "#{@timeout} seconds. The max pool size is currently #{@size}; " +
93
+ "consider increasing the pool size or timeout."
94
+ end
95
+
96
+ @connection_mutex.synchronize do
97
+ socket = if @checked_out.size < @sockets.size
98
+ checkout_existing_socket
99
+ elsif @sockets.size < @size
100
+ checkout_new_socket
101
+ end
102
+
103
+ return socket if socket
104
+
105
+ # Otherwise, wait
106
+ if @logger
107
+ @logger.warn "MONGODB Waiting for available connection; " +
108
+ "#{@checked_out.size} of #{@size} connections checked out."
109
+ end
110
+ @queue.wait(@connection_mutex)
111
+ end
112
+ end
113
+ end
114
+ end
115
+ end
@@ -11,10 +11,9 @@ Gem::Specification.new do |s|
11
11
 
12
12
  s.require_paths = ['lib']
13
13
 
14
- s.files = ['README.rdoc', 'HISTORY', 'Rakefile',
15
- 'mongo.gemspec', 'LICENSE.txt']
14
+ s.files = ['README.md', 'Rakefile', 'mongo.gemspec', 'LICENSE.txt']
16
15
  s.files += ['lib/mongo.rb'] + Dir['lib/mongo/**/*.rb']
17
- s.files += Dir['examples/**/*.rb'] + Dir['bin/**/*.rb']
16
+ s.files += Dir['docs/**/*.md'] + Dir['examples/**/*.rb'] + Dir['bin/**/*.rb']
18
17
  s.files += Dir['bin/mongo_console']
19
18
  s.test_files = Dir['test/**/*.rb']
20
19
 
@@ -25,8 +24,8 @@ Gem::Specification.new do |s|
25
24
  s.test_files -= Dir['test/mongo_bson/*.rb'] # remove these files from the manifest
26
25
 
27
26
  s.has_rdoc = true
28
- s.rdoc_options = ['--main', 'README.rdoc', '--inline-source']
29
- s.extra_rdoc_files = ['README.rdoc']
27
+ s.rdoc_options = ['--main', 'README.md', '--inline-source']
28
+ s.extra_rdoc_files = ['README.md']
30
29
 
31
30
  s.authors = ['Jim Menard', 'Mike Dirolf', 'Kyle Banker']
32
31
  s.email = 'mongodb-dev@googlegroups.com'
@@ -437,11 +437,13 @@ class BSONTest < Test::Unit::TestCase
437
437
  # HashWithIndifferentAccess can cause problems for _id but not for other
438
438
  # keys. rather than require rails to test with HWIA directly, we do this
439
439
  # somewhat hacky test.
440
+ #
441
+ # Note that the driver only eliminates duplicate ids when move_id is true.
440
442
  def test_no_duplicate_id
441
443
  dup = {"_id" => "foo", :_id => "foo"}
442
444
  one = {"_id" => "foo"}
443
445
 
444
- assert_equal @encoder.serialize(one).to_a, @encoder.serialize(dup).to_a
446
+ assert_equal @encoder.serialize(one, false, true).to_a, @encoder.serialize(dup, false, true).to_a
445
447
  end
446
448
 
447
449
  def test_duplicate_keys
@@ -26,6 +26,20 @@ class TestCollection < Test::Unit::TestCase
26
26
  assert @coll.pk_factory.is_a?(Object)
27
27
  end
28
28
 
29
+ class TestPK
30
+ def self.create_pk
31
+ end
32
+ end
33
+
34
+ def test_pk_factory_on_collection
35
+ @coll = Collection.new(@@db, 'foo', TestPK)
36
+ assert_equal TestPK, @coll.pk_factory
37
+
38
+
39
+ @coll2 = Collection.new(@@db, 'foo', :pk => TestPK)
40
+ assert_equal TestPK, @coll2.pk_factory
41
+ end
42
+
29
43
  def test_valid_names
30
44
  assert_raise Mongo::InvalidNSName do
31
45
  @@db["te$t"]
@@ -628,6 +642,22 @@ class TestCollection < Test::Unit::TestCase
628
642
  assert @collection.index_information['a_1']['unique'] == true
629
643
  end
630
644
 
645
+ should "drop duplicates" do
646
+ @collection.insert({:a => 1})
647
+ @collection.insert({:a => 1})
648
+ assert_equal 2, @collection.find({:a => 1}).count
649
+ @collection.create_index([['a', Mongo::ASCENDING]], :unique => true, :dropDups => true)
650
+ assert_equal 1, @collection.find({:a => 1}).count
651
+ end
652
+
653
+ should "drop duplicates with ruby-like drop_dups key" do
654
+ @collection.insert({:a => 1})
655
+ @collection.insert({:a => 1})
656
+ assert_equal 2, @collection.find({:a => 1}).count
657
+ @collection.create_index([['a', Mongo::ASCENDING]], :unique => true, :drop_dups => true)
658
+ assert_equal 1, @collection.find({:a => 1}).count
659
+ end
660
+
631
661
  should "create an index in the background" do
632
662
  if @@version > '1.3.1'
633
663
  @collection.create_index([['b', Mongo::ASCENDING]], :background => true)
@@ -9,15 +9,15 @@ class TestConnection < Test::Unit::TestCase
9
9
  include BSON
10
10
 
11
11
  def setup
12
- @mongo = standard_connection
12
+ @conn = standard_connection
13
13
  end
14
14
 
15
15
  def teardown
16
- @mongo[MONGO_TEST_DB].get_last_error
16
+ @conn[MONGO_TEST_DB].get_last_error
17
17
  end
18
18
 
19
19
  def test_server_info
20
- server_info = @mongo.server_info
20
+ server_info = @conn.server_info
21
21
  assert server_info.keys.include?("version")
22
22
  assert Mongo::Support.ok?(server_info)
23
23
  end
@@ -29,63 +29,76 @@ class TestConnection < Test::Unit::TestCase
29
29
  end
30
30
 
31
31
  def test_server_version
32
- assert_match /\d\.\d+(\.\d+)?/, @mongo.server_version.to_s
32
+ assert_match /\d\.\d+(\.\d+)?/, @conn.server_version.to_s
33
33
  end
34
34
 
35
35
  def test_invalid_database_names
36
- assert_raise TypeError do @mongo.db(4) end
36
+ assert_raise TypeError do @conn.db(4) end
37
37
 
38
- assert_raise Mongo::InvalidNSName do @mongo.db('') end
39
- assert_raise Mongo::InvalidNSName do @mongo.db('te$t') end
40
- assert_raise Mongo::InvalidNSName do @mongo.db('te.t') end
41
- assert_raise Mongo::InvalidNSName do @mongo.db('te\\t') end
42
- assert_raise Mongo::InvalidNSName do @mongo.db('te/t') end
43
- assert_raise Mongo::InvalidNSName do @mongo.db('te st') end
38
+ assert_raise Mongo::InvalidNSName do @conn.db('') end
39
+ assert_raise Mongo::InvalidNSName do @conn.db('te$t') end
40
+ assert_raise Mongo::InvalidNSName do @conn.db('te.t') end
41
+ assert_raise Mongo::InvalidNSName do @conn.db('te\\t') end
42
+ assert_raise Mongo::InvalidNSName do @conn.db('te/t') end
43
+ assert_raise Mongo::InvalidNSName do @conn.db('te st') end
44
+ end
45
+
46
+ def test_replica_set_connection_name
47
+ assert_raise_error(Mongo::ReplicaSetConnectionError, "replSet") do
48
+ standard_connection(:name => "replica-set-foo")
49
+ end
50
+ end
51
+
52
+ def test_options_passed_to_db
53
+ @pk_mock = Object.new
54
+ db = @conn.db('test', :pk => @pk_mock, :strict => true)
55
+ assert_equal @pk_mock, db.pk_factory
56
+ assert db.strict?
44
57
  end
45
58
 
46
59
  def test_database_info
47
- @mongo.drop_database(MONGO_TEST_DB)
48
- @mongo.db(MONGO_TEST_DB).collection('info-test').insert('a' => 1)
60
+ @conn.drop_database(MONGO_TEST_DB)
61
+ @conn.db(MONGO_TEST_DB).collection('info-test').insert('a' => 1)
49
62
 
50
- info = @mongo.database_info
63
+ info = @conn.database_info
51
64
  assert_not_nil info
52
65
  assert_kind_of Hash, info
53
66
  assert_not_nil info[MONGO_TEST_DB]
54
67
  assert info[MONGO_TEST_DB] > 0
55
68
 
56
- @mongo.drop_database(MONGO_TEST_DB)
69
+ @conn.drop_database(MONGO_TEST_DB)
57
70
  end
58
71
 
59
72
  def test_copy_database
60
- @mongo.db('old').collection('copy-test').insert('a' => 1)
61
- @mongo.copy_database('old', 'new', host_port)
62
- old_object = @mongo.db('old').collection('copy-test').find.next_document
63
- new_object = @mongo.db('new').collection('copy-test').find.next_document
73
+ @conn.db('old').collection('copy-test').insert('a' => 1)
74
+ @conn.copy_database('old', 'new', host_port)
75
+ old_object = @conn.db('old').collection('copy-test').find.next_document
76
+ new_object = @conn.db('new').collection('copy-test').find.next_document
64
77
  assert_equal old_object, new_object
65
- @mongo.drop_database('old')
66
- @mongo.drop_database('new')
78
+ @conn.drop_database('old')
79
+ @conn.drop_database('new')
67
80
  end
68
81
 
69
82
  def test_copy_database_with_auth
70
- @mongo.db('old').collection('copy-test').insert('a' => 1)
71
- @mongo.db('old').add_user('bob', 'secret')
83
+ @conn.db('old').collection('copy-test').insert('a' => 1)
84
+ @conn.db('old').add_user('bob', 'secret')
72
85
 
73
86
  assert_raise Mongo::OperationFailure do
74
- @mongo.copy_database('old', 'new', host_port, 'bob', 'badpassword')
87
+ @conn.copy_database('old', 'new', host_port, 'bob', 'badpassword')
75
88
  end
76
89
 
77
- result = @mongo.copy_database('old', 'new', host_port, 'bob', 'secret')
90
+ result = @conn.copy_database('old', 'new', host_port, 'bob', 'secret')
78
91
  assert Mongo::Support.ok?(result)
79
92
 
80
- @mongo.drop_database('old')
81
- @mongo.drop_database('new')
93
+ @conn.drop_database('old')
94
+ @conn.drop_database('new')
82
95
  end
83
96
 
84
97
  def test_database_names
85
- @mongo.drop_database(MONGO_TEST_DB)
86
- @mongo.db(MONGO_TEST_DB).collection('info-test').insert('a' => 1)
98
+ @conn.drop_database(MONGO_TEST_DB)
99
+ @conn.db(MONGO_TEST_DB).collection('info-test').insert('a' => 1)
87
100
 
88
- names = @mongo.database_names
101
+ names = @conn.database_names
89
102
  assert_not_nil names
90
103
  assert_kind_of Array, names
91
104
  assert names.length >= 1
@@ -112,15 +125,15 @@ class TestConnection < Test::Unit::TestCase
112
125
  end
113
126
 
114
127
  def test_drop_database
115
- db = @mongo.db('ruby-mongo-will-be-deleted')
128
+ db = @conn.db('ruby-mongo-will-be-deleted')
116
129
  coll = db.collection('temp')
117
130
  coll.remove
118
131
  coll.insert(:name => 'temp')
119
132
  assert_equal 1, coll.count()
120
- assert @mongo.database_names.include?('ruby-mongo-will-be-deleted')
133
+ assert @conn.database_names.include?('ruby-mongo-will-be-deleted')
121
134
 
122
- @mongo.drop_database('ruby-mongo-will-be-deleted')
123
- assert !@mongo.database_names.include?('ruby-mongo-will-be-deleted')
135
+ @conn.drop_database('ruby-mongo-will-be-deleted')
136
+ assert !@conn.database_names.include?('ruby-mongo-will-be-deleted')
124
137
  end
125
138
 
126
139
  def test_nodes
@@ -138,15 +151,15 @@ class TestConnection < Test::Unit::TestCase
138
151
  end
139
152
 
140
153
  def test_fsync_lock
141
- assert !@mongo.locked?
142
- @mongo.lock!
143
- assert @mongo.locked?
144
- assert_equal 1, @mongo['admin']['$cmd.sys.inprog'].find_one['fsyncLock'], "Not fsync-locked"
145
- assert_equal "unlock requested", @mongo.unlock!['info']
154
+ assert !@conn.locked?
155
+ @conn.lock!
156
+ assert @conn.locked?
157
+ assert_equal 1, @conn['admin']['$cmd.sys.inprog'].find_one['fsyncLock'], "Not fsync-locked"
158
+ assert_equal "unlock requested", @conn.unlock!['info']
146
159
  unlocked = false
147
160
  counter = 0
148
161
  while counter < 5
149
- if @mongo['admin']['$cmd.sys.inprog'].find_one['fsyncLock'].nil?
162
+ if @conn['admin']['$cmd.sys.inprog'].find_one['fsyncLock'].nil?
150
163
  unlocked = true
151
164
  break
152
165
  else
@@ -154,7 +167,7 @@ class TestConnection < Test::Unit::TestCase
154
167
  counter += 1
155
168
  end
156
169
  end
157
- assert !@mongo.locked?
170
+ assert !@conn.locked?
158
171
  assert unlocked, "mongod failed to unlock"
159
172
  end
160
173
 
@@ -192,35 +205,35 @@ class TestConnection < Test::Unit::TestCase
192
205
 
193
206
  context "Connection exceptions" do
194
207
  setup do
195
- @conn = standard_connection(:pool_size => 10, :timeout => 10)
196
- @coll = @conn[MONGO_TEST_DB]['test-connection-exceptions']
208
+ @con = standard_connection(:pool_size => 10, :timeout => 10)
209
+ @coll = @con[MONGO_TEST_DB]['test-connection-exceptions']
197
210
  end
198
211
 
199
212
  should "release connection if an exception is raised on send_message" do
200
- @conn.stubs(:send_message_on_socket).raises(ConnectionFailure)
201
- assert_equal 0, @conn.checked_out.size
213
+ @con.stubs(:send_message_on_socket).raises(ConnectionFailure)
214
+ assert_equal 0, @con.checked_out.size
202
215
  assert_raise ConnectionFailure do
203
216
  @coll.insert({:test => "insert"})
204
217
  end
205
- assert_equal 0, @conn.checked_out.size
218
+ assert_equal 0, @con.checked_out.size
206
219
  end
207
220
 
208
221
  should "release connection if an exception is raised on send_with_safe_check" do
209
- @conn.stubs(:receive).raises(ConnectionFailure)
210
- assert_equal 0, @conn.checked_out.size
222
+ @con.stubs(:receive).raises(ConnectionFailure)
223
+ assert_equal 0, @con.checked_out.size
211
224
  assert_raise ConnectionFailure do
212
225
  @coll.insert({:test => "insert"}, :safe => true)
213
226
  end
214
- assert_equal 0, @conn.checked_out.size
227
+ assert_equal 0, @con.checked_out.size
215
228
  end
216
229
 
217
230
  should "release connection if an exception is raised on receive_message" do
218
- @conn.stubs(:receive).raises(ConnectionFailure)
219
- assert_equal 0, @conn.checked_out.size
231
+ @con.stubs(:receive).raises(ConnectionFailure)
232
+ assert_equal 0, @con.checked_out.size
220
233
  assert_raise ConnectionFailure do
221
234
  @coll.find.to_a
222
235
  end
223
- assert_equal 0, @conn.checked_out.size
236
+ assert_equal 0, @con.checked_out.size
224
237
  end
225
238
  end
226
239
  end