mongo 1.0 → 1.1.5
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/LICENSE.txt +1 -13
- data/{README.rdoc → README.md} +129 -149
- data/Rakefile +94 -58
- data/bin/mongo_console +21 -0
- data/docs/1.0_UPGRADE.md +21 -0
- data/docs/CREDITS.md +123 -0
- data/docs/FAQ.md +112 -0
- data/docs/GridFS.md +158 -0
- data/docs/HISTORY.md +185 -0
- data/docs/REPLICA_SETS.md +75 -0
- data/docs/TUTORIAL.md +247 -0
- data/docs/WRITE_CONCERN.md +28 -0
- data/lib/mongo/collection.rb +225 -105
- data/lib/mongo/connection.rb +374 -315
- data/lib/mongo/cursor.rb +122 -77
- data/lib/mongo/db.rb +109 -85
- data/lib/mongo/exceptions.rb +6 -0
- data/lib/mongo/gridfs/grid.rb +19 -11
- data/lib/mongo/gridfs/grid_ext.rb +36 -9
- data/lib/mongo/gridfs/grid_file_system.rb +15 -9
- data/lib/mongo/gridfs/grid_io.rb +49 -16
- data/lib/mongo/gridfs/grid_io_fix.rb +38 -0
- data/lib/mongo/repl_set_connection.rb +290 -0
- data/lib/mongo/util/conversions.rb +3 -1
- data/lib/mongo/util/core_ext.rb +17 -4
- data/lib/mongo/util/pool.rb +125 -0
- data/lib/mongo/util/server_version.rb +2 -0
- data/lib/mongo/util/support.rb +12 -0
- data/lib/mongo/util/uri_parser.rb +71 -0
- data/lib/mongo.rb +23 -7
- data/{mongo-ruby-driver.gemspec → mongo.gemspec} +9 -7
- data/test/auxillary/1.4_features.rb +2 -2
- data/test/auxillary/authentication_test.rb +1 -1
- data/test/auxillary/autoreconnect_test.rb +1 -1
- data/test/{slave_connection_test.rb → auxillary/slave_connection_test.rb} +6 -6
- data/test/bson/binary_test.rb +15 -0
- data/test/bson/bson_test.rb +537 -0
- data/test/bson/byte_buffer_test.rb +190 -0
- data/test/bson/hash_with_indifferent_access_test.rb +38 -0
- data/test/bson/json_test.rb +17 -0
- data/test/bson/object_id_test.rb +141 -0
- data/test/bson/ordered_hash_test.rb +197 -0
- data/test/collection_test.rb +195 -15
- data/test/connection_test.rb +93 -56
- data/test/conversions_test.rb +1 -1
- data/test/cursor_fail_test.rb +75 -0
- data/test/cursor_message_test.rb +43 -0
- data/test/cursor_test.rb +93 -32
- data/test/db_api_test.rb +28 -55
- data/test/db_connection_test.rb +2 -3
- data/test/db_test.rb +45 -40
- data/test/grid_file_system_test.rb +14 -6
- data/test/grid_io_test.rb +36 -7
- data/test/grid_test.rb +54 -10
- data/test/replica_sets/connect_test.rb +84 -0
- data/test/replica_sets/count_test.rb +35 -0
- data/test/{replica → replica_sets}/insert_test.rb +17 -14
- data/test/replica_sets/pooled_insert_test.rb +55 -0
- data/test/replica_sets/query_secondaries.rb +80 -0
- data/test/replica_sets/query_test.rb +41 -0
- data/test/replica_sets/replication_ack_test.rb +64 -0
- data/test/replica_sets/rs_test_helper.rb +29 -0
- data/test/safe_test.rb +68 -0
- data/test/support/hash_with_indifferent_access.rb +199 -0
- data/test/support/keys.rb +45 -0
- data/test/support_test.rb +19 -0
- data/test/test_helper.rb +53 -15
- data/test/threading/{test_threading_large_pool.rb → threading_with_large_pool_test.rb} +2 -2
- data/test/threading_test.rb +2 -2
- data/test/tools/repl_set_manager.rb +241 -0
- data/test/tools/test.rb +13 -0
- data/test/unit/collection_test.rb +70 -7
- data/test/unit/connection_test.rb +18 -39
- data/test/unit/cursor_test.rb +7 -8
- data/test/unit/db_test.rb +14 -17
- data/test/unit/grid_test.rb +49 -0
- data/test/unit/pool_test.rb +9 -0
- data/test/unit/repl_set_connection_test.rb +82 -0
- data/test/unit/safe_test.rb +125 -0
- metadata +132 -51
- data/bin/bson_benchmark.rb +0 -59
- data/bin/fail_if_no_c.rb +0 -11
- data/examples/admin.rb +0 -43
- data/examples/capped.rb +0 -22
- data/examples/cursor.rb +0 -48
- data/examples/gridfs.rb +0 -44
- data/examples/index_test.rb +0 -126
- data/examples/info.rb +0 -31
- data/examples/queries.rb +0 -70
- data/examples/simple.rb +0 -24
- data/examples/strict.rb +0 -35
- data/examples/types.rb +0 -36
- data/test/replica/count_test.rb +0 -34
- data/test/replica/pooled_insert_test.rb +0 -54
- data/test/replica/query_test.rb +0 -39
data/lib/mongo/gridfs/grid_io.rb
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# encoding: UTF-8
|
|
2
|
+
|
|
1
3
|
# --
|
|
2
4
|
# Copyright (C) 2008-2010 10gen Inc.
|
|
3
5
|
#
|
|
@@ -30,7 +32,7 @@ module Mongo
|
|
|
30
32
|
PROTECTED_ATTRS = [:files_id, :file_length, :client_md5, :server_md5]
|
|
31
33
|
|
|
32
34
|
attr_reader :content_type, :chunk_size, :upload_date, :files_id, :filename,
|
|
33
|
-
:metadata, :server_md5, :client_md5, :file_length
|
|
35
|
+
:metadata, :server_md5, :client_md5, :file_length, :file_position
|
|
34
36
|
|
|
35
37
|
# Create a new GridIO object. Note that most users will not need to use this class directly;
|
|
36
38
|
# the Grid and GridFileSystem classes will instantiate this class
|
|
@@ -43,14 +45,14 @@ module Mongo
|
|
|
43
45
|
# @option opts [Hash] :query a query selector used when opening the file in 'r' mode.
|
|
44
46
|
# @option opts [Hash] :query_opts any query options to be used when opening the file in 'r' mode.
|
|
45
47
|
# @option opts [String] :fs_name the file system prefix.
|
|
46
|
-
# @
|
|
47
|
-
# @
|
|
48
|
-
# @
|
|
48
|
+
# @option opts [Integer] (262144) :chunk_size size of file chunks in bytes.
|
|
49
|
+
# @option opts [Hash] :metadata ({}) any additional data to store with the file.
|
|
50
|
+
# @option opts [ObjectId] :_id (ObjectId) a unique id for
|
|
49
51
|
# the file to be use in lieu of an automatically generated one.
|
|
50
|
-
# @
|
|
52
|
+
# @option opts [String] :content_type ('binary/octet-stream') If no content type is specified,
|
|
51
53
|
# the content type will may be inferred from the filename extension if the mime-types gem can be
|
|
52
54
|
# loaded. Otherwise, the content type 'binary/octet-stream' will be used.
|
|
53
|
-
# @
|
|
55
|
+
# @option opts [Boolean] :safe (false) When safe mode is enabled, the chunks sent to the server
|
|
54
56
|
# will be validated using an md5 hash. If validation fails, an exception will be raised.
|
|
55
57
|
def initialize(files, chunks, filename, mode, opts={})
|
|
56
58
|
@files = files
|
|
@@ -178,7 +180,7 @@ module Mongo
|
|
|
178
180
|
# This method will be invoked automatically when
|
|
179
181
|
# on GridIO#open is passed a block. Otherwise, it must be called manually.
|
|
180
182
|
#
|
|
181
|
-
# @return [BSON::
|
|
183
|
+
# @return [BSON::ObjectId]
|
|
182
184
|
def close
|
|
183
185
|
if @mode[0] == ?w
|
|
184
186
|
if @current_chunk['n'].zero? && @chunk_position.zero?
|
|
@@ -190,6 +192,23 @@ module Mongo
|
|
|
190
192
|
id
|
|
191
193
|
end
|
|
192
194
|
|
|
195
|
+
# Read a chunk of the data from the file and yield it to the given
|
|
196
|
+
# block.
|
|
197
|
+
#
|
|
198
|
+
# Note that this method reads from the current file position.
|
|
199
|
+
#
|
|
200
|
+
# @yield Yields on chunk per iteration as defined by this file's
|
|
201
|
+
# chunk size.
|
|
202
|
+
#
|
|
203
|
+
# @return [Mongo::GridIO] self
|
|
204
|
+
def each
|
|
205
|
+
return read_all unless block_given?
|
|
206
|
+
while chunk = read(chunk_size)
|
|
207
|
+
yield chunk
|
|
208
|
+
end
|
|
209
|
+
self
|
|
210
|
+
end
|
|
211
|
+
|
|
193
212
|
def inspect
|
|
194
213
|
"#<GridIO _id: #{@files_id}>"
|
|
195
214
|
end
|
|
@@ -197,8 +216,8 @@ module Mongo
|
|
|
197
216
|
private
|
|
198
217
|
|
|
199
218
|
def create_chunk(n)
|
|
200
|
-
chunk = OrderedHash.new
|
|
201
|
-
chunk['_id'] = BSON::
|
|
219
|
+
chunk = BSON::OrderedHash.new
|
|
220
|
+
chunk['_id'] = BSON::ObjectId.new
|
|
202
221
|
chunk['n'] = n
|
|
203
222
|
chunk['files_id'] = @files_id
|
|
204
223
|
chunk['data'] = ''
|
|
@@ -223,11 +242,14 @@ module Mongo
|
|
|
223
242
|
# Read a file in its entirety.
|
|
224
243
|
def read_all
|
|
225
244
|
buf = ''
|
|
226
|
-
|
|
245
|
+
if @current_chunk
|
|
227
246
|
buf << @current_chunk['data'].to_s
|
|
228
|
-
|
|
229
|
-
|
|
247
|
+
while chunk = get_chunk(@current_chunk['n'] + 1)
|
|
248
|
+
buf << chunk['data'].to_s
|
|
249
|
+
@current_chunk = chunk
|
|
250
|
+
end
|
|
230
251
|
end
|
|
252
|
+
@file_position = @file_length
|
|
231
253
|
buf
|
|
232
254
|
end
|
|
233
255
|
|
|
@@ -235,7 +257,11 @@ module Mongo
|
|
|
235
257
|
def read_length(length)
|
|
236
258
|
cache_chunk_data
|
|
237
259
|
remaining = (@file_length - @file_position)
|
|
238
|
-
|
|
260
|
+
if length.nil?
|
|
261
|
+
to_read = remaining
|
|
262
|
+
else
|
|
263
|
+
to_read = length > remaining ? remaining : length
|
|
264
|
+
end
|
|
239
265
|
return nil unless remaining > 0
|
|
240
266
|
|
|
241
267
|
buf = ''
|
|
@@ -306,20 +332,27 @@ module Mongo
|
|
|
306
332
|
|
|
307
333
|
# Initialize the class for writing a file.
|
|
308
334
|
def init_write(opts)
|
|
309
|
-
@files_id = opts.delete(:_id) || BSON::
|
|
335
|
+
@files_id = opts.delete(:_id) || BSON::ObjectId.new
|
|
310
336
|
@content_type = opts.delete(:content_type) || (defined? MIME) && get_content_type || DEFAULT_CONTENT_TYPE
|
|
311
337
|
@chunk_size = opts.delete(:chunk_size) || DEFAULT_CHUNK_SIZE
|
|
312
338
|
@metadata = opts.delete(:metadata) if opts[:metadata]
|
|
313
339
|
@aliases = opts.delete(:aliases) if opts[:aliases]
|
|
314
340
|
@file_length = 0
|
|
315
341
|
opts.each {|k, v| self[k] = v}
|
|
342
|
+
check_existing_file if @safe
|
|
316
343
|
|
|
317
344
|
@current_chunk = create_chunk(0)
|
|
318
345
|
@file_position = 0
|
|
319
346
|
end
|
|
320
347
|
|
|
348
|
+
def check_existing_file
|
|
349
|
+
if @files.find_one('_id' => @files_id)
|
|
350
|
+
raise GridError, "Attempting to overwrite with Grid#put. You must delete the file first."
|
|
351
|
+
end
|
|
352
|
+
end
|
|
353
|
+
|
|
321
354
|
def to_mongo_object
|
|
322
|
-
h = OrderedHash.new
|
|
355
|
+
h = BSON::OrderedHash.new
|
|
323
356
|
h['_id'] = @files_id
|
|
324
357
|
h['filename'] = @filename if @filename
|
|
325
358
|
h['contentType'] = @content_type
|
|
@@ -335,7 +368,7 @@ module Mongo
|
|
|
335
368
|
|
|
336
369
|
# Get a server-side md5 and validate against the client if running in safe mode.
|
|
337
370
|
def get_md5
|
|
338
|
-
md5_command = OrderedHash.new
|
|
371
|
+
md5_command = BSON::OrderedHash.new
|
|
339
372
|
md5_command['filemd5'] = @files_id
|
|
340
373
|
md5_command['root'] = @fs_name
|
|
341
374
|
@server_md5 = @files.db.command(md5_command)['md5']
|
|
@@ -0,0 +1,38 @@
|
|
|
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
|
+
|
|
19
|
+
module Mongo
|
|
20
|
+
class GridIO
|
|
21
|
+
|
|
22
|
+
# This fixes a comparson issue in JRuby 1.9
|
|
23
|
+
def get_md5
|
|
24
|
+
md5_command = BSON::OrderedHash.new
|
|
25
|
+
md5_command['filemd5'] = @files_id
|
|
26
|
+
md5_command['root'] = @fs_name
|
|
27
|
+
@server_md5 = @files.db.command(md5_command)['md5']
|
|
28
|
+
if @safe
|
|
29
|
+
@client_md5 = @local_md5.hexdigest
|
|
30
|
+
if @local_md5.to_s != @server_md5.to_s
|
|
31
|
+
raise GridMD5Failure, "File on server failed MD5 check"
|
|
32
|
+
end
|
|
33
|
+
else
|
|
34
|
+
@server_md5
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
@@ -0,0 +1,290 @@
|
|
|
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
|
+
|
|
19
|
+
module Mongo
|
|
20
|
+
|
|
21
|
+
# Instantiates and manages connections to a MongoDB replica set.
|
|
22
|
+
class ReplSetConnection < Connection
|
|
23
|
+
attr_reader :nodes, :secondaries, :arbiters, :read_pool, :secondary_pools
|
|
24
|
+
|
|
25
|
+
# Create a connection to a MongoDB replica set.
|
|
26
|
+
#
|
|
27
|
+
# Once connected to a replica set, you can find out which nodes are primary, secondary, and
|
|
28
|
+
# arbiters with the corresponding accessors: Connection#primary, Connection#secondaries, and
|
|
29
|
+
# Connection#arbiters. This is useful if your application needs to connect manually to nodes other
|
|
30
|
+
# than the primary.
|
|
31
|
+
#
|
|
32
|
+
# @param [Array] args A list of host-port pairs ending with a hash containing any options. See
|
|
33
|
+
# the examples below for exactly how to use the constructor.
|
|
34
|
+
#
|
|
35
|
+
# @option options [Boolean, Hash] :safe (false) Set the default safe-mode options
|
|
36
|
+
# propogated to DB objects instantiated off of this Connection. This
|
|
37
|
+
# default can be overridden upon instantiation of any DB by explicity setting a :safe value
|
|
38
|
+
# on initialization.
|
|
39
|
+
# @option options [Boolean] :read_secondary(false) If true, a random secondary node will be chosen,
|
|
40
|
+
# and all reads will be directed to that node.
|
|
41
|
+
# @option options [Logger, #debug] :logger (nil) Logger instance to receive driver operation log.
|
|
42
|
+
# @option options [Integer] :pool_size (1) The maximum number of socket connections allowed per
|
|
43
|
+
# connection pool. Note: this setting is relevant only for multi-threaded applications.
|
|
44
|
+
# @option options [Float] :timeout (5.0) When all of the connections a pool are checked out,
|
|
45
|
+
# this is the number of seconds to wait for a new connection to be released before throwing an exception.
|
|
46
|
+
# Note: this setting is relevant only for multi-threaded applications.
|
|
47
|
+
#
|
|
48
|
+
# @example Connect to a replica set and provide two seed nodes:
|
|
49
|
+
# ReplSetConnection.new(['localhost', 30000], ['localhost', 30001])
|
|
50
|
+
#
|
|
51
|
+
# @example Connect to a replica set providing two seed nodes and allowing reads from a
|
|
52
|
+
# secondary node:
|
|
53
|
+
# ReplSetConnection.new(['localhost', 30000], ['localhost', 30001], :read_secondary => true)
|
|
54
|
+
#
|
|
55
|
+
# @see http://api.mongodb.org/ruby/current/file.REPLICA_SETS.html Replica sets in Ruby
|
|
56
|
+
#
|
|
57
|
+
# @raise [ReplicaSetConnectionError] This is raised if a replica set name is specified and the
|
|
58
|
+
# driver fails to connect to a replica set with that name.
|
|
59
|
+
def initialize(*args)
|
|
60
|
+
if args.last.is_a?(Hash)
|
|
61
|
+
opts = args.pop
|
|
62
|
+
else
|
|
63
|
+
opts = {}
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
unless args.length > 0
|
|
67
|
+
raise MongoArgumentError, "A ReplSetConnection requires at least one node."
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# Get seed nodes
|
|
71
|
+
@nodes = args
|
|
72
|
+
|
|
73
|
+
# Replica set name
|
|
74
|
+
@replica_set = opts[:rs_name]
|
|
75
|
+
|
|
76
|
+
# Cache the various node types when connecting to a replica set.
|
|
77
|
+
@secondaries = []
|
|
78
|
+
@arbiters = []
|
|
79
|
+
|
|
80
|
+
# Connection pools for each secondary node
|
|
81
|
+
@secondary_pools = []
|
|
82
|
+
@read_pool = nil
|
|
83
|
+
|
|
84
|
+
# Are we allowing reads from secondaries?
|
|
85
|
+
@read_secondary = opts.fetch(:read_secondary, false)
|
|
86
|
+
|
|
87
|
+
setup(opts)
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# Create a new socket and attempt to connect to master.
|
|
91
|
+
# If successful, sets host and port to master and returns the socket.
|
|
92
|
+
#
|
|
93
|
+
# If connecting to a replica set, this method will replace the
|
|
94
|
+
# initially-provided seed list with any nodes known to the set.
|
|
95
|
+
#
|
|
96
|
+
# @raise [ConnectionFailure] if unable to connect to any host or port.
|
|
97
|
+
def connect
|
|
98
|
+
reset_connection
|
|
99
|
+
@nodes_to_try = @nodes.clone
|
|
100
|
+
|
|
101
|
+
while connecting?
|
|
102
|
+
node = @nodes_to_try.shift
|
|
103
|
+
config = check_is_master(node)
|
|
104
|
+
|
|
105
|
+
if is_primary?(config)
|
|
106
|
+
set_primary(node)
|
|
107
|
+
else
|
|
108
|
+
set_auxillary(node, config)
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
pick_secondary_for_read if @read_secondary
|
|
113
|
+
|
|
114
|
+
if !connected?
|
|
115
|
+
if @secondary_pools.empty?
|
|
116
|
+
raise ConnectionFailure, "Failed to connect any given host:port"
|
|
117
|
+
else
|
|
118
|
+
raise ConnectionFailure, "Failed to connect to primary node."
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def connecting?
|
|
124
|
+
@nodes_to_try.length > 0
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
# Close the connection to the database.
|
|
128
|
+
def close
|
|
129
|
+
super
|
|
130
|
+
@read_pool = nil
|
|
131
|
+
@secondary_pools.each do |pool|
|
|
132
|
+
pool.close
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
# If a ConnectionFailure is raised, this method will be called
|
|
137
|
+
# to close the connection and reset connection values.
|
|
138
|
+
# TODO: what's the point of this method?
|
|
139
|
+
def reset_connection
|
|
140
|
+
super
|
|
141
|
+
@secondaries = []
|
|
142
|
+
@secondary_pools = []
|
|
143
|
+
@arbiters = []
|
|
144
|
+
@nodes_tried = []
|
|
145
|
+
@nodes_to_try = []
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
# Is it okay to connect to a slave?
|
|
149
|
+
#
|
|
150
|
+
# @return [Boolean]
|
|
151
|
+
def slave_ok?
|
|
152
|
+
@read_secondary || @slave_ok
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
private
|
|
156
|
+
|
|
157
|
+
def check_is_master(node)
|
|
158
|
+
begin
|
|
159
|
+
host, port = *node
|
|
160
|
+
socket = TCPSocket.new(host, port)
|
|
161
|
+
socket.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
|
|
162
|
+
|
|
163
|
+
config = self['admin'].command({:ismaster => 1}, :sock => socket)
|
|
164
|
+
|
|
165
|
+
check_set_name(config, socket)
|
|
166
|
+
rescue OperationFailure, SocketError, SystemCallError, IOError => ex
|
|
167
|
+
close unless connected?
|
|
168
|
+
ensure
|
|
169
|
+
@nodes_tried << node
|
|
170
|
+
if config
|
|
171
|
+
nodes = []
|
|
172
|
+
nodes += config['hosts'] if config['hosts']
|
|
173
|
+
nodes += config['arbiters'] if config['arbiters']
|
|
174
|
+
nodes += config['passives'] if config['passives']
|
|
175
|
+
update_node_list(nodes)
|
|
176
|
+
|
|
177
|
+
if config['msg'] && @logger
|
|
178
|
+
@logger.warn("MONGODB #{config['msg']}")
|
|
179
|
+
end
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
socket.close if socket
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
config
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
# Primary, when connecting to a replica can, can only be a true primary node.
|
|
189
|
+
# (And not a slave, which is possible when connecting with the standard
|
|
190
|
+
# Connection class.
|
|
191
|
+
def is_primary?(config)
|
|
192
|
+
config && (config['ismaster'] == 1 || config['ismaster'] == true)
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
# Pick a node randomly from the set of possible secondaries.
|
|
196
|
+
def pick_secondary_for_read
|
|
197
|
+
if (size = @secondary_pools.size) > 0
|
|
198
|
+
@read_pool = @secondary_pools[rand(size)]
|
|
199
|
+
end
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
# Make sure that we're connected to the expected replica set.
|
|
203
|
+
def check_set_name(config, socket)
|
|
204
|
+
if @replica_set
|
|
205
|
+
config = self['admin'].command({:replSetGetStatus => 1},
|
|
206
|
+
:sock => socket, :check_response => false)
|
|
207
|
+
|
|
208
|
+
if !Mongo::Support.ok?(config)
|
|
209
|
+
raise ReplicaSetConnectionError, config['errmsg']
|
|
210
|
+
elsif config['set'] != @replica_set
|
|
211
|
+
raise ReplicaSetConnectionError,
|
|
212
|
+
"Attempting to connect to replica set '#{config['set']}' but expected '#{@replica_set}'"
|
|
213
|
+
end
|
|
214
|
+
end
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
# Determines what kind of node we have and caches its host
|
|
218
|
+
# and port so that users can easily connect manually.
|
|
219
|
+
def set_auxillary(node, config)
|
|
220
|
+
if config
|
|
221
|
+
if config['secondary']
|
|
222
|
+
host, port = *node
|
|
223
|
+
@secondaries << node unless @secondaries.include?(node)
|
|
224
|
+
@secondary_pools << Pool.new(self, host, port, :size => @pool_size, :timeout => @timeout)
|
|
225
|
+
elsif config['arbiterOnly']
|
|
226
|
+
@arbiters << node unless @arbiters.include?(node)
|
|
227
|
+
end
|
|
228
|
+
end
|
|
229
|
+
end
|
|
230
|
+
|
|
231
|
+
# Update the list of known nodes. Only applies to replica sets,
|
|
232
|
+
# where the response to the ismaster command will return a list
|
|
233
|
+
# of known hosts.
|
|
234
|
+
#
|
|
235
|
+
# @param hosts [Array] a list of hosts, specified as string-encoded
|
|
236
|
+
# host-port values. Example: ["myserver-1.org:27017", "myserver-1.org:27017"]
|
|
237
|
+
#
|
|
238
|
+
# @return [Array] the updated list of nodes
|
|
239
|
+
def update_node_list(hosts)
|
|
240
|
+
new_nodes = hosts.map do |host|
|
|
241
|
+
if !host.respond_to?(:split)
|
|
242
|
+
warn "Could not parse host #{host.inspect}."
|
|
243
|
+
next
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
host, port = host.split(':')
|
|
247
|
+
[host, port ? port.to_i : Connection::DEFAULT_PORT]
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
# Replace the list of seed nodes with the canonical list.
|
|
251
|
+
@nodes = new_nodes.clone
|
|
252
|
+
|
|
253
|
+
@nodes_to_try = new_nodes - @nodes_tried
|
|
254
|
+
end
|
|
255
|
+
|
|
256
|
+
# Checkout a socket for reading (i.e., a secondary node).
|
|
257
|
+
def checkout_reader
|
|
258
|
+
connect unless connected?
|
|
259
|
+
|
|
260
|
+
if @read_pool
|
|
261
|
+
@read_pool.checkout
|
|
262
|
+
else
|
|
263
|
+
checkout_writer
|
|
264
|
+
end
|
|
265
|
+
end
|
|
266
|
+
|
|
267
|
+
# Checkout a socket for writing (i.e., a primary node).
|
|
268
|
+
def checkout_writer
|
|
269
|
+
connect unless connected?
|
|
270
|
+
|
|
271
|
+
@primary_pool.checkout
|
|
272
|
+
end
|
|
273
|
+
|
|
274
|
+
# Checkin a socket used for reading.
|
|
275
|
+
def checkin_reader(socket)
|
|
276
|
+
if @read_pool
|
|
277
|
+
@read_pool.checkin(socket)
|
|
278
|
+
else
|
|
279
|
+
checkin_writer(socket)
|
|
280
|
+
end
|
|
281
|
+
end
|
|
282
|
+
|
|
283
|
+
# Checkin a socket used for writing.
|
|
284
|
+
def checkin_writer(socket)
|
|
285
|
+
if @primary_pool
|
|
286
|
+
@primary_pool.checkin(socket)
|
|
287
|
+
end
|
|
288
|
+
end
|
|
289
|
+
end
|
|
290
|
+
end
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# encoding: UTF-8
|
|
2
|
+
|
|
1
3
|
# --
|
|
2
4
|
# Copyright (C) 2008-2010 10gen Inc.
|
|
3
5
|
#
|
|
@@ -31,7 +33,7 @@ module Mongo #:nodoc:
|
|
|
31
33
|
# <tt>array_as_sort_parameters([["field1", :asc], ["field2", :desc]])</tt> =>
|
|
32
34
|
# <tt>{ "field1" => 1, "field2" => -1}</tt>
|
|
33
35
|
def array_as_sort_parameters(value)
|
|
34
|
-
order_by = OrderedHash.new
|
|
36
|
+
order_by = BSON::OrderedHash.new
|
|
35
37
|
if value.first.is_a? Array
|
|
36
38
|
value.each do |param|
|
|
37
39
|
if (param.class.name == "String")
|
data/lib/mongo/util/core_ext.rb
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# encoding: UTF-8
|
|
2
|
+
|
|
1
3
|
# --
|
|
2
4
|
# Copyright (C) 2008-2010 10gen Inc.
|
|
3
5
|
#
|
|
@@ -18,10 +20,21 @@
|
|
|
18
20
|
class Object
|
|
19
21
|
|
|
20
22
|
#:nodoc:
|
|
21
|
-
def
|
|
22
|
-
yield
|
|
23
|
-
|
|
24
|
-
end
|
|
23
|
+
def tap
|
|
24
|
+
yield self
|
|
25
|
+
self
|
|
26
|
+
end unless respond_to? :tap
|
|
27
|
+
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
#:nodoc:
|
|
31
|
+
module Enumerable
|
|
32
|
+
|
|
33
|
+
#:nodoc:
|
|
34
|
+
def each_with_object(memo)
|
|
35
|
+
each { |element| yield(element, memo) }
|
|
36
|
+
memo
|
|
37
|
+
end unless [].respond_to?(:each_with_object)
|
|
25
38
|
|
|
26
39
|
end
|
|
27
40
|
|
|
@@ -0,0 +1,125 @@
|
|
|
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
|
+
attr_accessor :host, :port, :size, :timeout, :safe, :checked_out
|
|
22
|
+
|
|
23
|
+
# Create a new pool of connections.
|
|
24
|
+
#
|
|
25
|
+
def initialize(connection, host, port, options={})
|
|
26
|
+
@connection = connection
|
|
27
|
+
|
|
28
|
+
@host, @port = host, port
|
|
29
|
+
|
|
30
|
+
# Pool size and timeout.
|
|
31
|
+
@size = options[:size] || 1
|
|
32
|
+
@timeout = options[:timeout] || 5.0
|
|
33
|
+
|
|
34
|
+
# Mutex for synchronizing pool access
|
|
35
|
+
@connection_mutex = Mutex.new
|
|
36
|
+
|
|
37
|
+
# Condition variable for signal and wait
|
|
38
|
+
@queue = ConditionVariable.new
|
|
39
|
+
|
|
40
|
+
@sockets = []
|
|
41
|
+
@checked_out = []
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def close
|
|
45
|
+
@sockets.each do |sock|
|
|
46
|
+
begin
|
|
47
|
+
sock.close
|
|
48
|
+
rescue IOError => ex
|
|
49
|
+
warn "IOError when attempting to close socket connected "
|
|
50
|
+
+ "to #{@host}:#{@port}: #{ex.inspect}"
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
@host = @port = nil
|
|
54
|
+
@sockets.clear
|
|
55
|
+
@checked_out.clear
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Return a socket to the pool.
|
|
59
|
+
def checkin(socket)
|
|
60
|
+
@connection_mutex.synchronize do
|
|
61
|
+
@checked_out.delete(socket)
|
|
62
|
+
@queue.signal
|
|
63
|
+
end
|
|
64
|
+
true
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# Adds a new socket to the pool and checks it out.
|
|
68
|
+
#
|
|
69
|
+
# This method is called exclusively from #checkout;
|
|
70
|
+
# therefore, it runs within a mutex.
|
|
71
|
+
def checkout_new_socket
|
|
72
|
+
begin
|
|
73
|
+
socket = TCPSocket.new(@host, @port)
|
|
74
|
+
socket.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
|
|
75
|
+
rescue => ex
|
|
76
|
+
raise ConnectionFailure, "Failed to connect socket: #{ex}"
|
|
77
|
+
end
|
|
78
|
+
@sockets << socket
|
|
79
|
+
@checked_out << socket
|
|
80
|
+
socket
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# Checks out the first available socket from the pool.
|
|
84
|
+
#
|
|
85
|
+
# This method is called exclusively from #checkout;
|
|
86
|
+
# therefore, it runs within a mutex.
|
|
87
|
+
def checkout_existing_socket
|
|
88
|
+
socket = (@sockets - @checked_out).first
|
|
89
|
+
@checked_out << socket
|
|
90
|
+
socket
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# Check out an existing socket or create a new socket if the maximum
|
|
94
|
+
# pool size has not been exceeded. Otherwise, wait for the next
|
|
95
|
+
# available socket.
|
|
96
|
+
def checkout
|
|
97
|
+
@connection.connect if !@connection.connected?
|
|
98
|
+
start_time = Time.now
|
|
99
|
+
loop do
|
|
100
|
+
if (Time.now - start_time) > @timeout
|
|
101
|
+
raise ConnectionTimeoutError, "could not obtain connection within " +
|
|
102
|
+
"#{@timeout} seconds. The max pool size is currently #{@size}; " +
|
|
103
|
+
"consider increasing the pool size or timeout."
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
@connection_mutex.synchronize do
|
|
107
|
+
socket = if @checked_out.size < @sockets.size
|
|
108
|
+
checkout_existing_socket
|
|
109
|
+
elsif @sockets.size < @size
|
|
110
|
+
checkout_new_socket
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
return socket if socket
|
|
114
|
+
|
|
115
|
+
# Otherwise, wait
|
|
116
|
+
if @logger
|
|
117
|
+
@logger.warn "MONGODB Waiting for available connection; " +
|
|
118
|
+
"#{@checked_out.size} of #{@size} connections checked out."
|
|
119
|
+
end
|
|
120
|
+
@queue.wait(@connection_mutex)
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
end
|
data/lib/mongo/util/support.rb
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# encoding: UTF-8
|
|
2
|
+
|
|
1
3
|
# --
|
|
2
4
|
# Copyright (C) 2008-2010 10gen Inc.
|
|
3
5
|
#
|
|
@@ -66,5 +68,15 @@ module Mongo
|
|
|
66
68
|
"[['field1', '(ascending|descending)'], ['field2', '(ascending|descending)']]"
|
|
67
69
|
end
|
|
68
70
|
end
|
|
71
|
+
|
|
72
|
+
# Determine if a database command has succeeded by
|
|
73
|
+
# checking the document response.
|
|
74
|
+
#
|
|
75
|
+
# @param [Hash] doc
|
|
76
|
+
#
|
|
77
|
+
# @return [Boolean] true if the 'ok' key is either 1 or *true*.
|
|
78
|
+
def ok?(doc)
|
|
79
|
+
doc['ok'] == 1.0 || doc['ok'] == true
|
|
80
|
+
end
|
|
69
81
|
end
|
|
70
82
|
end
|