mongo 1.10.0-java

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