mongo 1.10.0-java

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.
Files changed (116) hide show
  1. checksums.yaml +7 -0
  2. checksums.yaml.gz.sig +0 -0
  3. data.tar.gz.sig +0 -0
  4. data/LICENSE +190 -0
  5. data/README.md +149 -0
  6. data/Rakefile +31 -0
  7. data/VERSION +1 -0
  8. data/bin/mongo_console +43 -0
  9. data/ext/jsasl/target/jsasl.jar +0 -0
  10. data/lib/mongo.rb +90 -0
  11. data/lib/mongo/bulk_write_collection_view.rb +380 -0
  12. data/lib/mongo/collection.rb +1164 -0
  13. data/lib/mongo/collection_writer.rb +364 -0
  14. data/lib/mongo/connection.rb +19 -0
  15. data/lib/mongo/connection/node.rb +239 -0
  16. data/lib/mongo/connection/pool.rb +347 -0
  17. data/lib/mongo/connection/pool_manager.rb +325 -0
  18. data/lib/mongo/connection/sharding_pool_manager.rb +67 -0
  19. data/lib/mongo/connection/socket.rb +18 -0
  20. data/lib/mongo/connection/socket/socket_util.rb +37 -0
  21. data/lib/mongo/connection/socket/ssl_socket.rb +95 -0
  22. data/lib/mongo/connection/socket/tcp_socket.rb +86 -0
  23. data/lib/mongo/connection/socket/unix_socket.rb +39 -0
  24. data/lib/mongo/cursor.rb +719 -0
  25. data/lib/mongo/db.rb +735 -0
  26. data/lib/mongo/exception.rb +88 -0
  27. data/lib/mongo/functional.rb +21 -0
  28. data/lib/mongo/functional/authentication.rb +318 -0
  29. data/lib/mongo/functional/logging.rb +85 -0
  30. data/lib/mongo/functional/read_preference.rb +174 -0
  31. data/lib/mongo/functional/sasl_java.rb +48 -0
  32. data/lib/mongo/functional/uri_parser.rb +374 -0
  33. data/lib/mongo/functional/write_concern.rb +66 -0
  34. data/lib/mongo/gridfs.rb +18 -0
  35. data/lib/mongo/gridfs/grid.rb +112 -0
  36. data/lib/mongo/gridfs/grid_ext.rb +53 -0
  37. data/lib/mongo/gridfs/grid_file_system.rb +163 -0
  38. data/lib/mongo/gridfs/grid_io.rb +484 -0
  39. data/lib/mongo/legacy.rb +140 -0
  40. data/lib/mongo/mongo_client.rb +702 -0
  41. data/lib/mongo/mongo_replica_set_client.rb +523 -0
  42. data/lib/mongo/mongo_sharded_client.rb +159 -0
  43. data/lib/mongo/networking.rb +370 -0
  44. data/lib/mongo/utils.rb +19 -0
  45. data/lib/mongo/utils/conversions.rb +110 -0
  46. data/lib/mongo/utils/core_ext.rb +70 -0
  47. data/lib/mongo/utils/server_version.rb +69 -0
  48. data/lib/mongo/utils/support.rb +80 -0
  49. data/lib/mongo/utils/thread_local_variable_manager.rb +25 -0
  50. data/mongo.gemspec +36 -0
  51. data/test/functional/authentication_test.rb +35 -0
  52. data/test/functional/bulk_api_stress_test.rb +133 -0
  53. data/test/functional/bulk_write_collection_view_test.rb +1129 -0
  54. data/test/functional/client_test.rb +565 -0
  55. data/test/functional/collection_test.rb +2073 -0
  56. data/test/functional/collection_writer_test.rb +83 -0
  57. data/test/functional/conversions_test.rb +163 -0
  58. data/test/functional/cursor_fail_test.rb +63 -0
  59. data/test/functional/cursor_message_test.rb +57 -0
  60. data/test/functional/cursor_test.rb +625 -0
  61. data/test/functional/db_api_test.rb +819 -0
  62. data/test/functional/db_connection_test.rb +27 -0
  63. data/test/functional/db_test.rb +344 -0
  64. data/test/functional/grid_file_system_test.rb +285 -0
  65. data/test/functional/grid_io_test.rb +252 -0
  66. data/test/functional/grid_test.rb +273 -0
  67. data/test/functional/pool_test.rb +62 -0
  68. data/test/functional/safe_test.rb +98 -0
  69. data/test/functional/ssl_test.rb +29 -0
  70. data/test/functional/support_test.rb +62 -0
  71. data/test/functional/timeout_test.rb +58 -0
  72. data/test/functional/uri_test.rb +330 -0
  73. data/test/functional/write_concern_test.rb +118 -0
  74. data/test/helpers/general.rb +50 -0
  75. data/test/helpers/test_unit.rb +317 -0
  76. data/test/replica_set/authentication_test.rb +35 -0
  77. data/test/replica_set/basic_test.rb +174 -0
  78. data/test/replica_set/client_test.rb +341 -0
  79. data/test/replica_set/complex_connect_test.rb +77 -0
  80. data/test/replica_set/connection_test.rb +138 -0
  81. data/test/replica_set/count_test.rb +64 -0
  82. data/test/replica_set/cursor_test.rb +212 -0
  83. data/test/replica_set/insert_test.rb +140 -0
  84. data/test/replica_set/max_values_test.rb +145 -0
  85. data/test/replica_set/pinning_test.rb +55 -0
  86. data/test/replica_set/query_test.rb +73 -0
  87. data/test/replica_set/read_preference_test.rb +214 -0
  88. data/test/replica_set/refresh_test.rb +175 -0
  89. data/test/replica_set/replication_ack_test.rb +94 -0
  90. data/test/replica_set/ssl_test.rb +32 -0
  91. data/test/sharded_cluster/basic_test.rb +197 -0
  92. data/test/shared/authentication/basic_auth_shared.rb +286 -0
  93. data/test/shared/authentication/bulk_api_auth_shared.rb +259 -0
  94. data/test/shared/authentication/gssapi_shared.rb +164 -0
  95. data/test/shared/authentication/sasl_plain_shared.rb +96 -0
  96. data/test/shared/ssl_shared.rb +235 -0
  97. data/test/test_helper.rb +56 -0
  98. data/test/threading/basic_test.rb +120 -0
  99. data/test/tools/mongo_config.rb +608 -0
  100. data/test/tools/mongo_config_test.rb +160 -0
  101. data/test/unit/client_test.rb +347 -0
  102. data/test/unit/collection_test.rb +166 -0
  103. data/test/unit/connection_test.rb +325 -0
  104. data/test/unit/cursor_test.rb +299 -0
  105. data/test/unit/db_test.rb +136 -0
  106. data/test/unit/grid_test.rb +76 -0
  107. data/test/unit/mongo_sharded_client_test.rb +48 -0
  108. data/test/unit/node_test.rb +93 -0
  109. data/test/unit/pool_manager_test.rb +142 -0
  110. data/test/unit/read_pref_test.rb +115 -0
  111. data/test/unit/read_test.rb +159 -0
  112. data/test/unit/safe_test.rb +158 -0
  113. data/test/unit/sharding_pool_manager_test.rb +84 -0
  114. data/test/unit/write_concern_test.rb +175 -0
  115. metadata +260 -0
  116. metadata.gz.sig +0 -0
@@ -0,0 +1,174 @@
1
+ # Copyright (C) 2009-2013 MongoDB, Inc.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ module Mongo
16
+ module ReadPreference
17
+ READ_PREFERENCES = [
18
+ :primary,
19
+ :primary_preferred,
20
+ :secondary,
21
+ :secondary_preferred,
22
+ :nearest
23
+ ]
24
+
25
+ MONGOS_MODES = {
26
+ :primary => 'primary',
27
+ :primary_preferred => 'primaryPreferred',
28
+ :secondary => 'secondary',
29
+ :secondary_preferred => 'secondaryPreferred',
30
+ :nearest => 'nearest'
31
+ }
32
+
33
+ # Commands that may be sent to replica-set secondaries, depending on
34
+ # read preference and tags. All other commands are always run on the primary.
35
+ SECONDARY_OK_COMMANDS = [
36
+ 'group',
37
+ 'aggregate',
38
+ 'collstats',
39
+ 'dbstats',
40
+ 'count',
41
+ 'distinct',
42
+ 'geonear',
43
+ 'geosearch',
44
+ 'geowalk',
45
+ 'mapreduce',
46
+ 'replsetgetstatus',
47
+ 'ismaster',
48
+ 'parallelcollectionscan'
49
+ ]
50
+
51
+ def self.mongos(mode, tag_sets)
52
+ if mode != :secondary_preferred || !tag_sets.empty?
53
+ mongos_read_preference = BSON::OrderedHash[:mode => MONGOS_MODES[mode]]
54
+ mongos_read_preference[:tags] = tag_sets if !tag_sets.empty?
55
+ end
56
+ mongos_read_preference
57
+ end
58
+
59
+ def self.validate(value)
60
+ if READ_PREFERENCES.include?(value)
61
+ return true
62
+ else
63
+ raise MongoArgumentError, "#{value} is not a valid read preference. " +
64
+ "Please specify one of the following read preferences as a symbol: #{READ_PREFERENCES}"
65
+ end
66
+ end
67
+
68
+ # Returns true if it's ok to run the command on a secondary
69
+ def self.secondary_ok?(selector)
70
+ command = selector.keys.first.to_s.downcase
71
+
72
+ if command == 'mapreduce'
73
+ out = selector.select { |k, v| k.to_s.downcase == 'out' }.first.last
74
+ # the server only looks at the first key in the out object
75
+ return out.respond_to?(:keys) && out.keys.first.to_s.downcase == 'inline'
76
+ elsif command == 'aggregate'
77
+ return selector['pipeline'].none? { |op| op.key?('$out') || op.key?(:$out) }
78
+ end
79
+ SECONDARY_OK_COMMANDS.member?(command)
80
+ end
81
+
82
+ # Returns true if the command should be rerouted to the primary.
83
+ def self.reroute_cmd_primary?(read_pref, selector)
84
+ return false if read_pref == :primary
85
+ !secondary_ok?(selector)
86
+ end
87
+
88
+ # Given a command and read preference, possibly reroute to primary.
89
+ def self.cmd_read_pref(read_pref, selector)
90
+ ReadPreference::validate(read_pref)
91
+ if reroute_cmd_primary?(read_pref, selector)
92
+ warn "Database command '#{selector.keys.first}' rerouted to primary node"
93
+ read_pref = :primary
94
+ end
95
+ read_pref
96
+ end
97
+
98
+ def read_preference
99
+ {
100
+ :mode => @read,
101
+ :tags => @tag_sets,
102
+ :latency => @acceptable_latency
103
+ }
104
+ end
105
+
106
+ def read_pool(read_preference_override={})
107
+ return primary_pool if mongos?
108
+
109
+ read_pref = read_preference.merge(read_preference_override)
110
+
111
+ if pinned_pool && pinned_pool[:read_preference] == read_pref
112
+ pool = pinned_pool[:pool]
113
+ else
114
+ unpin_pool
115
+ pool = select_pool(read_pref)
116
+ end
117
+
118
+ unless pool
119
+ raise ConnectionFailure, "No replica set member available for query " +
120
+ "with read preference matching mode #{read_pref[:mode]} and tags " +
121
+ "matching #{read_pref[:tags]}."
122
+ end
123
+
124
+ pool
125
+ end
126
+
127
+ def select_pool(read_pref)
128
+ if read_pref[:mode] == :primary && !read_pref[:tags].empty?
129
+ raise MongoArgumentError, "Read preference :primary cannot be combined with tags"
130
+ end
131
+
132
+ case read_pref[:mode]
133
+ when :primary
134
+ primary_pool
135
+ when :primary_preferred
136
+ primary_pool || select_secondary_pool(secondary_pools, read_pref)
137
+ when :secondary
138
+ select_secondary_pool(secondary_pools, read_pref)
139
+ when :secondary_preferred
140
+ select_secondary_pool(secondary_pools, read_pref) || primary_pool
141
+ when :nearest
142
+ select_near_pool(pools, read_pref)
143
+ end
144
+ end
145
+
146
+ def select_secondary_pool(candidates, read_pref)
147
+ tag_sets = read_pref[:tags]
148
+
149
+ if !tag_sets.empty?
150
+ matches = []
151
+ tag_sets.detect do |tag_set|
152
+ matches = candidates.select do |candidate|
153
+ tag_set.none? { |k,v| candidate.tags[k.to_s] != v } &&
154
+ candidate.ping_time
155
+ end
156
+ !matches.empty?
157
+ end
158
+ else
159
+ matches = candidates
160
+ end
161
+
162
+ matches.empty? ? nil : select_near_pool(matches, read_pref)
163
+ end
164
+
165
+ def select_near_pool(candidates, read_pref)
166
+ latency = read_pref[:latency]
167
+ nearest_pool = candidates.min_by { |candidate| candidate.ping_time }
168
+ near_pools = candidates.select do |candidate|
169
+ (candidate.ping_time - nearest_pool.ping_time) <= latency
170
+ end
171
+ near_pools[ rand(near_pools.length) ]
172
+ end
173
+ end
174
+ end
@@ -0,0 +1,48 @@
1
+ # Copyright (C) 2009-2013 MongoDB, Inc.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ require 'jruby'
16
+
17
+ include Java
18
+
19
+ jar_dir = File.expand_path(File.join(File.dirname(__FILE__), '../../../ext/jsasl'))
20
+ require File.join(jar_dir, 'target/jsasl.jar')
21
+
22
+ module Mongo
23
+ module Sasl
24
+
25
+ module GSSAPI
26
+
27
+ def self.authenticate(username, client, socket, opts={})
28
+ db = client.db('$external')
29
+ hostname = socket.pool.host
30
+ servicename = opts[:gssapi_service_name] || 'mongodb'
31
+ canonicalize = opts[:canonicalize_host_name] ? opts[:canonicalize_host_name] : false
32
+
33
+ authenticator = org.mongodb.sasl.GSSAPIAuthenticator.new(JRuby.runtime, username, hostname, servicename, canonicalize)
34
+ token = BSON::Binary.new(authenticator.initialize_challenge)
35
+ cmd = BSON::OrderedHash['saslStart', 1, 'mechanism', 'GSSAPI', 'payload', token, 'autoAuthorize', 1]
36
+ response = db.command(cmd, :check_response => false, :socket => socket)
37
+
38
+ until response['done'] do
39
+ token = BSON::Binary.new(authenticator.evaluate_challenge(response['payload'].to_s))
40
+ cmd = BSON::OrderedHash['saslContinue', 1, 'conversationId', response['conversationId'], 'payload', token]
41
+ response = db.command(cmd, :check_response => false, :socket => socket)
42
+ end
43
+ response
44
+ end
45
+ end
46
+
47
+ end
48
+ end
@@ -0,0 +1,374 @@
1
+ # Copyright (C) 2009-2013 MongoDB, Inc.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ require 'cgi'
16
+ require 'uri'
17
+
18
+ module Mongo
19
+ class URIParser
20
+
21
+ AUTH_REGEX = /((.+)@)?/
22
+
23
+ HOST_REGEX = /([-.\w]+)|(\[[^\]]+\])/
24
+ PORT_REGEX = /(?::(\w+))?/
25
+ NODE_REGEX = /((#{HOST_REGEX}#{PORT_REGEX},?)+)/
26
+
27
+ PATH_REGEX = /(?:\/([-\w]+))?/
28
+
29
+ MONGODB_URI_MATCHER = /#{AUTH_REGEX}#{NODE_REGEX}#{PATH_REGEX}/
30
+ MONGODB_URI_SPEC = "mongodb://[username:password@]host1[:port1][,host2[:port2],...[,hostN[:portN]]][/[database][?options]]"
31
+
32
+ SPEC_ATTRS = [:nodes, :auths]
33
+
34
+ READ_PREFERENCES = {
35
+ 'primary' => :primary,
36
+ 'primarypreferred' => :primary_preferred,
37
+ 'secondary' => :secondary,
38
+ 'secondarypreferred' => :secondary_preferred,
39
+ 'nearest' => :nearest
40
+ }
41
+
42
+ OPT_ATTRS = [
43
+ :authmechanism,
44
+ :authsource,
45
+ :canonicalizehostname,
46
+ :connect,
47
+ :connecttimeoutms,
48
+ :fsync,
49
+ :gssapiservicename,
50
+ :journal,
51
+ :pool_size,
52
+ :readpreference,
53
+ :replicaset,
54
+ :safe,
55
+ :slaveok,
56
+ :sockettimeoutms,
57
+ :ssl,
58
+ :w,
59
+ :wtimeout,
60
+ :wtimeoutms
61
+ ]
62
+
63
+ OPT_VALID = {
64
+ :authmechanism => lambda { |arg| Mongo::Authentication.validate_mechanism(arg) },
65
+ :authsource => lambda { |arg| arg.length > 0 },
66
+ :canonicalizehostname => lambda { |arg| ['true', 'false'].include?(arg) },
67
+ :connect => lambda { |arg| [ 'direct', 'replicaset', 'true', 'false', true, false ].include?(arg) },
68
+ :connecttimeoutms => lambda { |arg| arg =~ /^\d+$/ },
69
+ :fsync => lambda { |arg| ['true', 'false'].include?(arg) },
70
+ :gssapiservicename => lambda { |arg| arg.length > 0 },
71
+ :journal => lambda { |arg| ['true', 'false'].include?(arg) },
72
+ :pool_size => lambda { |arg| arg.to_i > 0 },
73
+ :readpreference => lambda { |arg| READ_PREFERENCES.keys.include?(arg) },
74
+ :replicaset => lambda { |arg| arg.length > 0 },
75
+ :safe => lambda { |arg| ['true', 'false'].include?(arg) },
76
+ :slaveok => lambda { |arg| ['true', 'false'].include?(arg) },
77
+ :sockettimeoutms => lambda { |arg| arg =~ /^\d+$/ },
78
+ :ssl => lambda { |arg| ['true', 'false'].include?(arg) },
79
+ :w => lambda { |arg| arg =~ /^\w+$/ },
80
+ :wtimeout => lambda { |arg| arg =~ /^\d+$/ },
81
+ :wtimeoutms => lambda { |arg| arg =~ /^\d+$/ }
82
+ }
83
+
84
+ OPT_ERR = {
85
+ :authmechanism => "must be one of #{Mongo::Authentication::MECHANISMS.join(', ')}",
86
+ :authsource => "must be a string containing the name of the database being used for authentication",
87
+ :canonicalizehostname => "must be 'true' or 'false'",
88
+ :connect => "must be 'direct', 'replicaset', 'true', or 'false'",
89
+ :connecttimeoutms => "must be an integer specifying milliseconds",
90
+ :fsync => "must be 'true' or 'false'",
91
+ :gssapiservicename => "must be a string containing the name of the GSSAPI service",
92
+ :journal => "must be 'true' or 'false'",
93
+ :pool_size => "must be an integer greater than zero",
94
+ :readpreference => "must be on of #{READ_PREFERENCES.keys.map(&:inspect).join(",")}",
95
+ :replicaset => "must be a string containing the name of the replica set to connect to",
96
+ :safe => "must be 'true' or 'false'",
97
+ :slaveok => "must be 'true' or 'false'",
98
+ :settimeoutms => "must be an integer specifying milliseconds",
99
+ :ssl => "must be 'true' or 'false'",
100
+ :w => "must be an integer indicating number of nodes to replicate to or a string " +
101
+ "specifying that replication is required to the majority or nodes with a " +
102
+ "particilar getLastErrorMode.",
103
+ :wtimeout => "must be an integer specifying milliseconds",
104
+ :wtimeoutms => "must be an integer specifying milliseconds"
105
+ }
106
+
107
+ OPT_CONV = {
108
+ :authmechanism => lambda { |arg| arg.upcase },
109
+ :authsource => lambda { |arg| arg },
110
+ :canonicalizehostname => lambda { |arg| arg == 'true' ? true : false },
111
+ :connect => lambda { |arg| arg == 'false' ? false : arg }, # convert 'false' to FalseClass
112
+ :connecttimeoutms => lambda { |arg| arg.to_f / 1000 }, # stored as seconds
113
+ :fsync => lambda { |arg| arg == 'true' ? true : false },
114
+ :gssapiservicename => lambda { |arg| arg },
115
+ :journal => lambda { |arg| arg == 'true' ? true : false },
116
+ :pool_size => lambda { |arg| arg.to_i },
117
+ :readpreference => lambda { |arg| READ_PREFERENCES[arg] },
118
+ :replicaset => lambda { |arg| arg },
119
+ :safe => lambda { |arg| arg == 'true' ? true : false },
120
+ :slaveok => lambda { |arg| arg == 'true' ? true : false },
121
+ :sockettimeoutms => lambda { |arg| arg.to_f / 1000 }, # stored as seconds
122
+ :ssl => lambda { |arg| arg == 'true' ? true : false },
123
+ :w => lambda { |arg| Mongo::Support.is_i?(arg) ? arg.to_i : arg.to_sym },
124
+ :wtimeout => lambda { |arg| arg.to_i },
125
+ :wtimeoutms => lambda { |arg| arg.to_i }
126
+ }
127
+
128
+ attr_reader :auths,
129
+ :authmechanism,
130
+ :authsource,
131
+ :canonicalizehostname,
132
+ :connect,
133
+ :connecttimeoutms,
134
+ :db_name,
135
+ :fsync,
136
+ :gssapiservicename,
137
+ :journal,
138
+ :nodes,
139
+ :pool_size,
140
+ :readpreference,
141
+ :replicaset,
142
+ :safe,
143
+ :slaveok,
144
+ :sockettimeoutms,
145
+ :ssl,
146
+ :w,
147
+ :wtimeout,
148
+ :wtimeoutms
149
+
150
+ # Parse a MongoDB URI. This method is used by MongoClient.from_uri.
151
+ # Returns an array of nodes and an array of db authorizations, if applicable.
152
+ #
153
+ # @note Passwords can contain any character except for ','
154
+ #
155
+ # @param [String] uri The MongoDB URI string.
156
+ def initialize(uri)
157
+ if uri.start_with?('mongodb://')
158
+ uri = uri[10..-1]
159
+ else
160
+ raise MongoArgumentError, "MongoDB URI must match this spec: #{MONGODB_URI_SPEC}"
161
+ end
162
+
163
+ hosts, opts = uri.split('?')
164
+ parse_options(opts)
165
+ parse_hosts(hosts)
166
+ validate_connect
167
+ end
168
+
169
+ # Create a Mongo::MongoClient or a Mongo::MongoReplicaSetClient based on the URI.
170
+ #
171
+ # @note Don't confuse this with attribute getter method #connect.
172
+ #
173
+ # @return [MongoClient,MongoReplicaSetClient]
174
+ def connection(extra_opts={}, legacy = false, sharded = false)
175
+ opts = connection_options.merge!(extra_opts)
176
+ if(legacy)
177
+ if replicaset?
178
+ ReplSetConnection.new(node_strings, opts)
179
+ else
180
+ Connection.new(host, port, opts)
181
+ end
182
+ else
183
+ if sharded
184
+ MongoShardedClient.new(node_strings, opts)
185
+ elsif replicaset?
186
+ MongoReplicaSetClient.new(node_strings, opts)
187
+ else
188
+ MongoClient.new(host, port, opts)
189
+ end
190
+ end
191
+ end
192
+
193
+ # Whether this represents a replica set.
194
+ # @return [true,false]
195
+ def replicaset?
196
+ replicaset.is_a?(String) || nodes.length > 1
197
+ end
198
+
199
+ # Whether to immediately connect to the MongoDB node[s]. Defaults to true.
200
+ # @return [true, false]
201
+ def connect?
202
+ connect != false
203
+ end
204
+
205
+ # Whether this represents a direct connection.
206
+ #
207
+ # @note Specifying :connect => 'direct' has no effect... other than to raise an exception if other variables suggest a replicaset.
208
+ #
209
+ # @return [true,false]
210
+ def direct?
211
+ !replicaset?
212
+ end
213
+
214
+ # For direct connections, the host of the (only) node.
215
+ # @return [String]
216
+ def host
217
+ nodes[0][0]
218
+ end
219
+
220
+ # For direct connections, the port of the (only) node.
221
+ # @return [Integer]
222
+ def port
223
+ nodes[0][1].to_i
224
+ end
225
+
226
+ # Options that can be passed to MongoClient.new or MongoReplicaSetClient.new
227
+ # @return [Hash]
228
+ def connection_options
229
+ opts = {}
230
+
231
+ if @wtimeout
232
+ warn "Using wtimeout in a URI is deprecated, please use wtimeoutMS. It will be removed in v2.0."
233
+ opts[:wtimeout] = @wtimeout
234
+ end
235
+ opts[:wtimeout] = @wtimeoutms if @wtimeoutms
236
+
237
+ opts[:w] = 1 if @safe
238
+ opts[:w] = @w if @w
239
+ opts[:j] = @journal if @journal
240
+ opts[:fsync] = @fsync if @fsync
241
+
242
+ opts[:connect_timeout] = @connecttimeoutms if @connecttimeoutms
243
+ opts[:op_timeout] = @sockettimeoutms if @sockettimeoutms
244
+ opts[:pool_size] = @pool_size if @pool_size
245
+ opts[:read] = @readpreference if @readpreference
246
+
247
+ if @slaveok && !@readpreference
248
+ unless replicaset?
249
+ opts[:slave_ok] = true
250
+ else
251
+ opts[:read] = :secondary_preferred
252
+ end
253
+ end
254
+
255
+ if replicaset.is_a?(String)
256
+ opts[:name] = replicaset
257
+ end
258
+
259
+ opts[:db_name] = @db_name if @db_name
260
+ opts[:auths] = @auths if @auths
261
+ opts[:ssl] = @ssl if @ssl
262
+ opts[:connect] = connect?
263
+
264
+ opts
265
+ end
266
+
267
+ def node_strings
268
+ nodes.map { |node| node.join(':') }
269
+ end
270
+
271
+ private
272
+
273
+ def parse_hosts(uri_without_protocol)
274
+ @nodes = []
275
+ @auths = Set.new
276
+
277
+ unless matches = MONGODB_URI_MATCHER.match(uri_without_protocol)
278
+ raise MongoArgumentError,
279
+ "MongoDB URI must match this spec: #{MONGODB_URI_SPEC}"
280
+ end
281
+
282
+ user_info = matches[2].split(':') if matches[2]
283
+ host_info = matches[3].split(',')
284
+ @db_name = matches[8]
285
+
286
+ host_info.each do |host|
287
+ if host[0,1] == '['
288
+ host, port = host.split(']:') << MongoClient::DEFAULT_PORT
289
+ host = host.end_with?(']') ? host[1...-1] : host[1..-1]
290
+ else
291
+ host, port = host.split(':') << MongoClient::DEFAULT_PORT
292
+ end
293
+ unless port.to_s =~ /^\d+$/
294
+ raise MongoArgumentError,
295
+ "Invalid port #{port}; port must be specified as digits."
296
+ end
297
+ @nodes << [host, port.to_i]
298
+ end
299
+
300
+ if @nodes.empty?
301
+ raise MongoArgumentError,
302
+ "No nodes specified. Please ensure that you've provided at " +
303
+ "least one node."
304
+ end
305
+
306
+ # no user info to parse, exit here
307
+ return unless user_info
308
+
309
+ # check for url encoding for username and password
310
+ username, password = user_info
311
+ if user_info.size > 2 ||
312
+ (username && username.include?('@')) ||
313
+ (password && password.include?('@'))
314
+
315
+ raise MongoArgumentError,
316
+ "The characters ':' and '@' in a username or password " +
317
+ "must be escaped (RFC 2396)."
318
+ end
319
+
320
+ # if username exists, proceed adding to auth set
321
+ unless username.nil? || username.empty?
322
+ auth = Authentication.validate_credentials({
323
+ :db_name => @db_name,
324
+ :username => URI.unescape(username),
325
+ :password => password ? URI.unescape(password) : nil,
326
+ :source => @authsource,
327
+ :mechanism => @authmechanism
328
+ })
329
+ auth[:extra] = @canonicalizehostname ? { :canonicalize_host_name => @canonicalizehostname } : {}
330
+ auth[:extra].merge!(:gssapi_service_name => @gssapiservicename) if @gssapiservicename
331
+ @auths << auth
332
+ end
333
+ end
334
+
335
+ # This method uses the lambdas defined in OPT_VALID and OPT_CONV to validate
336
+ # and convert the given options.
337
+ def parse_options(string_opts)
338
+ # initialize instance variables for available options
339
+ OPT_VALID.keys.each { |k| instance_variable_set("@#{k}", nil) }
340
+
341
+ string_opts ||= ''
342
+
343
+ return if string_opts.empty?
344
+
345
+ if string_opts.include?(';') and string_opts.include?('&')
346
+ raise MongoArgumentError, 'must not mix URL separators ; and &'
347
+ end
348
+
349
+ opts = CGI.parse(string_opts).inject({}) do |memo, (key, value)|
350
+ value = value.first
351
+ memo[key.downcase.to_sym] = value.strip.downcase
352
+ memo
353
+ end
354
+
355
+ opts.each do |key, value|
356
+ if !OPT_ATTRS.include?(key)
357
+ raise MongoArgumentError, "Invalid Mongo URI option #{key}"
358
+ end
359
+ if OPT_VALID[key].call(value)
360
+ instance_variable_set("@#{key}", OPT_CONV[key].call(value))
361
+ else
362
+ raise MongoArgumentError, "Invalid value #{value.inspect} for #{key}: #{OPT_ERR[key]}"
363
+ end
364
+ end
365
+ end
366
+
367
+ def validate_connect
368
+ if replicaset? and @connect == 'direct'
369
+ # Make sure the user doesn't specify something contradictory
370
+ raise MongoArgumentError, "connect=direct conflicts with setting a replicaset name"
371
+ end
372
+ end
373
+ end
374
+ end