mongo-lyon 1.2.4
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 +190 -0
- data/README.md +344 -0
- data/Rakefile +202 -0
- data/bin/mongo_console +34 -0
- data/docs/1.0_UPGRADE.md +21 -0
- data/docs/CREDITS.md +123 -0
- data/docs/FAQ.md +116 -0
- data/docs/GridFS.md +158 -0
- data/docs/HISTORY.md +225 -0
- data/docs/REPLICA_SETS.md +72 -0
- data/docs/TUTORIAL.md +247 -0
- data/docs/WRITE_CONCERN.md +28 -0
- data/lib/mongo.rb +77 -0
- data/lib/mongo/collection.rb +872 -0
- data/lib/mongo/connection.rb +875 -0
- data/lib/mongo/cursor.rb +449 -0
- data/lib/mongo/db.rb +607 -0
- data/lib/mongo/exceptions.rb +68 -0
- data/lib/mongo/gridfs/grid.rb +106 -0
- data/lib/mongo/gridfs/grid_ext.rb +57 -0
- data/lib/mongo/gridfs/grid_file_system.rb +145 -0
- data/lib/mongo/gridfs/grid_io.rb +394 -0
- data/lib/mongo/gridfs/grid_io_fix.rb +38 -0
- data/lib/mongo/repl_set_connection.rb +342 -0
- data/lib/mongo/util/conversions.rb +89 -0
- data/lib/mongo/util/core_ext.rb +60 -0
- data/lib/mongo/util/pool.rb +185 -0
- data/lib/mongo/util/server_version.rb +71 -0
- data/lib/mongo/util/support.rb +82 -0
- data/lib/mongo/util/uri_parser.rb +181 -0
- data/lib/mongo/version.rb +3 -0
- data/mongo.gemspec +34 -0
- data/test/auxillary/1.4_features.rb +166 -0
- data/test/auxillary/authentication_test.rb +68 -0
- data/test/auxillary/autoreconnect_test.rb +41 -0
- data/test/auxillary/repl_set_auth_test.rb +58 -0
- data/test/auxillary/slave_connection_test.rb +36 -0
- data/test/auxillary/threaded_authentication_test.rb +101 -0
- data/test/bson/binary_test.rb +15 -0
- data/test/bson/bson_test.rb +614 -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 +154 -0
- data/test/bson/ordered_hash_test.rb +197 -0
- data/test/collection_test.rb +893 -0
- data/test/connection_test.rb +303 -0
- data/test/conversions_test.rb +120 -0
- data/test/cursor_fail_test.rb +75 -0
- data/test/cursor_message_test.rb +43 -0
- data/test/cursor_test.rb +457 -0
- data/test/db_api_test.rb +715 -0
- data/test/db_connection_test.rb +15 -0
- data/test/db_test.rb +287 -0
- data/test/grid_file_system_test.rb +244 -0
- data/test/grid_io_test.rb +120 -0
- data/test/grid_test.rb +200 -0
- data/test/load/thin/load.rb +24 -0
- data/test/load/unicorn/load.rb +23 -0
- data/test/replica_sets/connect_test.rb +86 -0
- data/test/replica_sets/connection_string_test.rb +32 -0
- data/test/replica_sets/count_test.rb +35 -0
- data/test/replica_sets/insert_test.rb +53 -0
- data/test/replica_sets/pooled_insert_test.rb +55 -0
- data/test/replica_sets/query_secondaries.rb +96 -0
- data/test/replica_sets/query_test.rb +51 -0
- data/test/replica_sets/replication_ack_test.rb +66 -0
- data/test/replica_sets/rs_test_helper.rb +27 -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 +83 -0
- data/test/threading/threading_with_large_pool_test.rb +90 -0
- data/test/threading_test.rb +87 -0
- data/test/tools/auth_repl_set_manager.rb +14 -0
- data/test/tools/repl_set_manager.rb +266 -0
- data/test/unit/collection_test.rb +130 -0
- data/test/unit/connection_test.rb +98 -0
- data/test/unit/cursor_test.rb +99 -0
- data/test/unit/db_test.rb +96 -0
- data/test/unit/grid_test.rb +49 -0
- data/test/unit/pool_test.rb +9 -0
- data/test/unit/repl_set_connection_test.rb +72 -0
- data/test/unit/safe_test.rb +125 -0
- data/test/uri_test.rb +91 -0
- metadata +202 -0
@@ -0,0 +1,60 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
# --
|
4
|
+
# Copyright (C) 2008-2011 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
|
+
#:nodoc:
|
20
|
+
class Object
|
21
|
+
|
22
|
+
#:nodoc:
|
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)
|
38
|
+
|
39
|
+
end
|
40
|
+
|
41
|
+
#:nodoc:
|
42
|
+
class Hash
|
43
|
+
|
44
|
+
#:nodoc:
|
45
|
+
def assert_valid_keys(*valid_keys)
|
46
|
+
unknown_keys = keys - [valid_keys].flatten
|
47
|
+
raise(ArgumentError, "Unknown key(s): #{unknown_keys.join(", ")}") unless unknown_keys.empty?
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
51
|
+
|
52
|
+
#:nodoc:
|
53
|
+
class String
|
54
|
+
|
55
|
+
#:nodoc:
|
56
|
+
def to_bson_code
|
57
|
+
BSON::Code.new(self)
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|
@@ -0,0 +1,185 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
# --
|
4
|
+
# Copyright (C) 2008-2011 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, :unix_socket_path
|
22
|
+
|
23
|
+
# Create a new pool of connections.
|
24
|
+
#
|
25
|
+
def initialize(connection, host, port, opts={})
|
26
|
+
@connection = connection
|
27
|
+
|
28
|
+
@host, @port = host, port
|
29
|
+
|
30
|
+
unless @port
|
31
|
+
@unix_socket_path = host
|
32
|
+
@host = nil
|
33
|
+
end
|
34
|
+
|
35
|
+
# Pool size and timeout.
|
36
|
+
@size = opts[:size] || 1
|
37
|
+
@timeout = opts[:timeout] || 5.0
|
38
|
+
|
39
|
+
# Mutex for synchronizing pool access
|
40
|
+
@connection_mutex = Mutex.new
|
41
|
+
|
42
|
+
# Condition variable for signal and wait
|
43
|
+
@queue = ConditionVariable.new
|
44
|
+
|
45
|
+
# Operations to perform on a socket
|
46
|
+
@socket_ops = Hash.new { |h, k| h[k] = [] }
|
47
|
+
|
48
|
+
@sockets = []
|
49
|
+
@checked_out = []
|
50
|
+
end
|
51
|
+
|
52
|
+
def close
|
53
|
+
@sockets.each do |sock|
|
54
|
+
begin
|
55
|
+
sock.close
|
56
|
+
rescue IOError => ex
|
57
|
+
warn "IOError when attempting to close socket connected to #{self.to_s}: #{ex.inspect}"
|
58
|
+
end
|
59
|
+
end
|
60
|
+
@host = @port = @unix_socket_path = nil
|
61
|
+
@sockets.clear
|
62
|
+
@checked_out.clear
|
63
|
+
end
|
64
|
+
|
65
|
+
# Return a socket to the pool.
|
66
|
+
def checkin(socket)
|
67
|
+
@connection_mutex.synchronize do
|
68
|
+
@checked_out.delete(socket)
|
69
|
+
@queue.signal
|
70
|
+
end
|
71
|
+
true
|
72
|
+
end
|
73
|
+
|
74
|
+
# Adds a new socket to the pool and checks it out.
|
75
|
+
#
|
76
|
+
# This method is called exclusively from #checkout;
|
77
|
+
# therefore, it runs within a mutex.
|
78
|
+
def checkout_new_socket
|
79
|
+
begin
|
80
|
+
if @unix_socket_path
|
81
|
+
socket = UNIXSocket.new(@unix_socket_path)
|
82
|
+
else
|
83
|
+
socket = TCPSocket.new(@host, @port)
|
84
|
+
socket.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
|
85
|
+
end
|
86
|
+
rescue => ex
|
87
|
+
raise ConnectionFailure, "Failed to connect to #{self.to_s}: #{ex}"
|
88
|
+
end
|
89
|
+
|
90
|
+
# If any saved authentications exist, we want to apply those
|
91
|
+
# when creating new sockets.
|
92
|
+
@connection.apply_saved_authentication(:socket => socket)
|
93
|
+
|
94
|
+
@sockets << socket
|
95
|
+
@checked_out << socket
|
96
|
+
socket
|
97
|
+
end
|
98
|
+
|
99
|
+
# If a user calls DB#authenticate, and several sockets exist,
|
100
|
+
# then we need a way to apply the authentication on each socket.
|
101
|
+
# So we store the apply_authentication method, and this will be
|
102
|
+
# applied right before the next use of each socket.
|
103
|
+
def authenticate_existing
|
104
|
+
@connection_mutex.synchronize do
|
105
|
+
@sockets.each do |socket|
|
106
|
+
@socket_ops[socket] << Proc.new do
|
107
|
+
@connection.apply_saved_authentication(:socket => socket)
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
# Store the logout op for each existing socket to be applied before
|
114
|
+
# the next use of each socket.
|
115
|
+
def logout_existing(db)
|
116
|
+
@connection_mutex.synchronize do
|
117
|
+
@sockets.each do |socket|
|
118
|
+
@socket_ops[socket] << Proc.new do
|
119
|
+
@connection.db(db).issue_logout(:socket => socket)
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
# Checks out the first available socket from the pool.
|
126
|
+
#
|
127
|
+
# This method is called exclusively from #checkout;
|
128
|
+
# therefore, it runs within a mutex.
|
129
|
+
def checkout_existing_socket
|
130
|
+
socket = (@sockets - @checked_out).first
|
131
|
+
@checked_out << socket
|
132
|
+
socket
|
133
|
+
end
|
134
|
+
|
135
|
+
# Check out an existing socket or create a new socket if the maximum
|
136
|
+
# pool size has not been exceeded. Otherwise, wait for the next
|
137
|
+
# available socket.
|
138
|
+
def checkout
|
139
|
+
@connection.connect if !@connection.connected?
|
140
|
+
start_time = Time.now
|
141
|
+
loop do
|
142
|
+
if (Time.now - start_time) > @timeout
|
143
|
+
raise ConnectionTimeoutError, "could not obtain connection within " +
|
144
|
+
"#{@timeout} seconds. The max pool size is currently #{@size}; " +
|
145
|
+
"consider increasing the pool size or timeout."
|
146
|
+
end
|
147
|
+
|
148
|
+
@connection_mutex.synchronize do
|
149
|
+
socket = if @checked_out.size < @sockets.size
|
150
|
+
checkout_existing_socket
|
151
|
+
elsif @sockets.size < @size
|
152
|
+
checkout_new_socket
|
153
|
+
end
|
154
|
+
|
155
|
+
if socket
|
156
|
+
|
157
|
+
# This calls all procs, in order, scoped to existing sockets.
|
158
|
+
# At the moment, we use this to lazily authenticate and
|
159
|
+
# logout existing socket connections.
|
160
|
+
@socket_ops[socket].reject! do |op|
|
161
|
+
op.call
|
162
|
+
end
|
163
|
+
|
164
|
+
return socket
|
165
|
+
else
|
166
|
+
# Otherwise, wait
|
167
|
+
if @logger
|
168
|
+
@logger.warn "MONGODB Waiting for available connection; " +
|
169
|
+
"#{@checked_out.size} of #{@size} connections checked out."
|
170
|
+
end
|
171
|
+
@queue.wait(@connection_mutex)
|
172
|
+
end
|
173
|
+
end
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
def to_s
|
178
|
+
if @unix_socket_path
|
179
|
+
"#{@unix_socket_path}"
|
180
|
+
else
|
181
|
+
"#{@host}:#{@port}"
|
182
|
+
end
|
183
|
+
end
|
184
|
+
end
|
185
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
# --
|
4
|
+
# Copyright (C) 2008-2011 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
|
+
# Simple class for comparing server versions.
|
20
|
+
class ServerVersion
|
21
|
+
include Comparable
|
22
|
+
|
23
|
+
def initialize(version)
|
24
|
+
@version = version
|
25
|
+
end
|
26
|
+
|
27
|
+
# Implements comparable.
|
28
|
+
def <=>(new)
|
29
|
+
local, new = self.to_a, to_array(new)
|
30
|
+
for n in 0...local.size do
|
31
|
+
break if elements_include_mods?(local[n], new[n])
|
32
|
+
if local[n] < new[n].to_i
|
33
|
+
result = -1
|
34
|
+
break;
|
35
|
+
elsif local[n] > new[n].to_i
|
36
|
+
result = 1
|
37
|
+
break;
|
38
|
+
end
|
39
|
+
end
|
40
|
+
result || 0
|
41
|
+
end
|
42
|
+
|
43
|
+
# Return an array representation of this server version.
|
44
|
+
def to_a
|
45
|
+
to_array(@version)
|
46
|
+
end
|
47
|
+
|
48
|
+
# Return a string representation of this server version.
|
49
|
+
def to_s
|
50
|
+
@version
|
51
|
+
end
|
52
|
+
|
53
|
+
private
|
54
|
+
|
55
|
+
# Returns true if any elements include mod symbols (-, +)
|
56
|
+
def elements_include_mods?(*elements)
|
57
|
+
elements.any? { |n| n =~ /[\-\+]/ }
|
58
|
+
end
|
59
|
+
|
60
|
+
# Converts argument to an array of integers,
|
61
|
+
# appending any mods as the final element.
|
62
|
+
def to_array(version)
|
63
|
+
array = version.split(".").map {|n| (n =~ /^\d+$/) ? n.to_i : n }
|
64
|
+
if array.last =~ /(\d+)([\-\+])/
|
65
|
+
array[array.length-1] = $1.to_i
|
66
|
+
array << $2
|
67
|
+
end
|
68
|
+
array
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
# --
|
4
|
+
# Copyright (C) 2008-2011 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
|
+
require 'digest/md5'
|
20
|
+
|
21
|
+
module Mongo
|
22
|
+
module Support
|
23
|
+
include Mongo::Conversions
|
24
|
+
extend self
|
25
|
+
|
26
|
+
# Generate an MD5 for authentication.
|
27
|
+
#
|
28
|
+
# @param [String] username
|
29
|
+
# @param [String] password
|
30
|
+
# @param [String] nonce
|
31
|
+
#
|
32
|
+
# @return [String] a key for db authentication.
|
33
|
+
def auth_key(username, password, nonce)
|
34
|
+
Digest::MD5.hexdigest("#{nonce}#{username}#{hash_password(username, password)}")
|
35
|
+
end
|
36
|
+
|
37
|
+
# Return a hashed password for auth.
|
38
|
+
#
|
39
|
+
# @param [String] username
|
40
|
+
# @param [String] plaintext
|
41
|
+
#
|
42
|
+
# @return [String]
|
43
|
+
def hash_password(username, plaintext)
|
44
|
+
Digest::MD5.hexdigest("#{username}:mongo:#{plaintext}")
|
45
|
+
end
|
46
|
+
|
47
|
+
|
48
|
+
def validate_db_name(db_name)
|
49
|
+
unless [String, Symbol].include?(db_name.class)
|
50
|
+
raise TypeError, "db_name must be a string or symbol"
|
51
|
+
end
|
52
|
+
|
53
|
+
[" ", ".", "$", "/", "\\"].each do |invalid_char|
|
54
|
+
if db_name.include? invalid_char
|
55
|
+
raise Mongo::InvalidNSName, "database names cannot contain the character '#{invalid_char}'"
|
56
|
+
end
|
57
|
+
end
|
58
|
+
raise Mongo::InvalidNSName, "database name cannot be the empty string" if db_name.empty?
|
59
|
+
db_name
|
60
|
+
end
|
61
|
+
|
62
|
+
def format_order_clause(order)
|
63
|
+
case order
|
64
|
+
when String, Symbol then string_as_sort_parameters(order)
|
65
|
+
when Array then array_as_sort_parameters(order)
|
66
|
+
else
|
67
|
+
raise InvalidSortValueError, "Illegal sort clause, '#{order.class.name}'; must be of the form " +
|
68
|
+
"[['field1', '(ascending|descending)'], ['field2', '(ascending|descending)']]"
|
69
|
+
end
|
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
|
81
|
+
end
|
82
|
+
end
|
@@ -0,0 +1,181 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
# --
|
4
|
+
# Copyright (C) 2008-2011 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 URIParser
|
21
|
+
|
22
|
+
DEFAULT_PORT = 27017
|
23
|
+
MONGODB_URI_MATCHER = /(([-_.\w\d]+):([^@]+)@)?([-.\w\d]+)(:([\w\d]+))?(\/([-\d\w]+))?/
|
24
|
+
MONGODB_URI_SPEC = "mongodb://[username:password@]host1[:port1][,host2[:port2],...[,hostN[:portN]]][/database]"
|
25
|
+
SPEC_ATTRS = [:nodes, :auths]
|
26
|
+
OPT_ATTRS = [:connect, :replicaset, :slaveok, :safe, :w, :wtimeout, :fsync]
|
27
|
+
|
28
|
+
OPT_VALID = {:connect => lambda {|arg| ['direct', 'replicaset'].include?(arg)},
|
29
|
+
:replicaset => lambda {|arg| arg.length > 0},
|
30
|
+
:slaveok => lambda {|arg| ['true', 'false'].include?(arg)},
|
31
|
+
:safe => lambda {|arg| ['true', 'false'].include?(arg)},
|
32
|
+
:w => lambda {|arg| arg =~ /^\d+$/ },
|
33
|
+
:wtimeout => lambda {|arg| arg =~ /^\d+$/ },
|
34
|
+
:fsync => lambda {|arg| ['true', 'false'].include?(arg)}
|
35
|
+
}
|
36
|
+
|
37
|
+
OPT_ERR = {:connect => "must be 'direct' or 'replicaset'",
|
38
|
+
:replicaset => "must be a string containing the name of the replica set to connect to",
|
39
|
+
:slaveok => "must be 'true' or 'false'",
|
40
|
+
:safe => "must be 'true' or 'false'",
|
41
|
+
:w => "must be an integer specifying number of nodes to replica to",
|
42
|
+
:wtimeout => "must be an integer specifying milliseconds",
|
43
|
+
:fsync => "must be 'true' or 'false'"
|
44
|
+
}
|
45
|
+
|
46
|
+
OPT_CONV = {:connect => lambda {|arg| arg},
|
47
|
+
:replicaset => lambda {|arg| arg},
|
48
|
+
:slaveok => lambda {|arg| arg == 'true' ? true : false},
|
49
|
+
:safe => lambda {|arg| arg == 'true' ? true : false},
|
50
|
+
:w => lambda {|arg| arg.to_i},
|
51
|
+
:wtimeout => lambda {|arg| arg.to_i},
|
52
|
+
:fsync => lambda {|arg| arg == 'true' ? true : false}
|
53
|
+
}
|
54
|
+
|
55
|
+
attr_reader :nodes, :auths, :connect, :replicaset, :slaveok, :safe, :w, :wtimeout, :fsync
|
56
|
+
|
57
|
+
# Parse a MongoDB URI. This method is used by Connection.from_uri.
|
58
|
+
# Returns an array of nodes and an array of db authorizations, if applicable.
|
59
|
+
#
|
60
|
+
# Note: passwords can contain any character except for a ','.
|
61
|
+
#
|
62
|
+
# @core connections
|
63
|
+
def initialize(string)
|
64
|
+
if string =~ /^mongodb:\/\//
|
65
|
+
string = string[10..-1]
|
66
|
+
else
|
67
|
+
raise MongoArgumentError, "MongoDB URI must match this spec: #{MONGODB_URI_SPEC}"
|
68
|
+
end
|
69
|
+
|
70
|
+
hosts, opts = string.split('?')
|
71
|
+
parse_hosts(hosts)
|
72
|
+
parse_options(opts)
|
73
|
+
configure_connect
|
74
|
+
end
|
75
|
+
|
76
|
+
def connection_options
|
77
|
+
opts = {}
|
78
|
+
|
79
|
+
if (@w || @wtimeout || @fsync) && !@safe
|
80
|
+
raise MongoArgumentError, "Safe must be true if w, wtimeout, or fsync is specified"
|
81
|
+
end
|
82
|
+
|
83
|
+
if @safe
|
84
|
+
if @w || @wtimeout || @fsync
|
85
|
+
safe_opts = {}
|
86
|
+
safe_opts[:w] = @w if @w
|
87
|
+
safe_opts[:wtimeout] = @wtimeout if @wtimeout
|
88
|
+
safe_opts[:fsync] = @fsync if @fsync
|
89
|
+
else
|
90
|
+
safe_opts = true
|
91
|
+
end
|
92
|
+
|
93
|
+
opts[:safe] = safe_opts
|
94
|
+
end
|
95
|
+
|
96
|
+
if @slaveok
|
97
|
+
if @connect == 'direct'
|
98
|
+
opts[:slave_ok] = true
|
99
|
+
else
|
100
|
+
opts[:read_secondary] = true
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
opts[:rs_name] = @replicaset if @replicaset
|
105
|
+
|
106
|
+
opts
|
107
|
+
end
|
108
|
+
|
109
|
+
private
|
110
|
+
|
111
|
+
def parse_hosts(hosts)
|
112
|
+
@nodes = []
|
113
|
+
@auths = []
|
114
|
+
specs = hosts.split(',')
|
115
|
+
specs.each do |spec|
|
116
|
+
matches = MONGODB_URI_MATCHER.match(spec)
|
117
|
+
if !matches
|
118
|
+
raise MongoArgumentError, "MongoDB URI must match this spec: #{MONGODB_URI_SPEC}"
|
119
|
+
end
|
120
|
+
|
121
|
+
uname = matches[2]
|
122
|
+
pwd = matches[3]
|
123
|
+
host = matches[4]
|
124
|
+
port = matches[6] || DEFAULT_PORT
|
125
|
+
if !(port.to_s =~ /^\d+$/)
|
126
|
+
raise MongoArgumentError, "Invalid port #{port}; port must be specified as digits."
|
127
|
+
end
|
128
|
+
port = port.to_i
|
129
|
+
db = matches[8]
|
130
|
+
|
131
|
+
if uname && pwd && db
|
132
|
+
auths << {'db_name' => db, 'username' => uname, 'password' => pwd}
|
133
|
+
elsif uname || pwd || db
|
134
|
+
raise MongoArgumentError, "MongoDB URI must include all three of username, password, " +
|
135
|
+
"and db if any one of these is specified."
|
136
|
+
end
|
137
|
+
|
138
|
+
@nodes << [host, port]
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
# This method uses the lambdas defined in OPT_VALID and OPT_CONV to validate
|
143
|
+
# and convert the given options.
|
144
|
+
def parse_options(opts)
|
145
|
+
return unless opts
|
146
|
+
separator = opts.include?('&') ? '&' : ';'
|
147
|
+
opts.split(separator).each do |attr|
|
148
|
+
key, value = attr.split('=')
|
149
|
+
key = key.to_sym
|
150
|
+
value = value.strip.downcase
|
151
|
+
if !OPT_ATTRS.include?(key)
|
152
|
+
raise MongoArgumentError, "Invalid Mongo URI option #{key}"
|
153
|
+
end
|
154
|
+
|
155
|
+
if OPT_VALID[key].call(value)
|
156
|
+
instance_variable_set("@#{key}", OPT_CONV[key].call(value))
|
157
|
+
else
|
158
|
+
raise MongoArgumentError, "Invalid value for #{key}: #{OPT_ERR[key]}"
|
159
|
+
end
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
def configure_connect
|
164
|
+
if @nodes.length > 1 && !@connect
|
165
|
+
@connect = 'replicaset'
|
166
|
+
end
|
167
|
+
|
168
|
+
if !@connect
|
169
|
+
if @nodes.length > 1
|
170
|
+
@connect = 'replicaset'
|
171
|
+
else
|
172
|
+
@connect = 'direct'
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
if @connect == 'direct' && @replicaset
|
177
|
+
raise MongoArgumentError, "If specifying a replica set name, please also specify that connect=replicaset"
|
178
|
+
end
|
179
|
+
end
|
180
|
+
end
|
181
|
+
end
|