mongo 1.1.1 → 1.1.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -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