Skip to content

Commit

Permalink
Support read preferences in URI parser.
Browse files Browse the repository at this point in the history
This will parse and add read preference options from the uri parser and
add them to the connection options when asking for them. This makes the
assumption that we must be in a replica set to set the options, and will
override the slaveOk flag if it is also present.

The fix for RUBY-541 will change this slightly to assume it can be set
for replica sets and sharded clients.

[ RUBY-547 ]
  • Loading branch information
durran committed Feb 11, 2013
1 parent 9c92ebd commit 56c2724
Show file tree
Hide file tree
Showing 2 changed files with 128 additions and 61 deletions.
143 changes: 82 additions & 61 deletions lib/mongo/util/uri_parser.rb
Original file line number Diff line number Diff line change
Expand Up @@ -18,83 +18,100 @@ class URIParser

SPEC_ATTRS = [:nodes, :auths]

READ_PREFERENCES = {
"primary" => :primary,
"primarypreferred" => :primary_preferred,
"secondary" => :secondary,
"secondarypreferred" => :secondary_preferred,
"nearest" => :nearest
}

OPT_ATTRS = [
:connect,
:connecttimeoutms,
:fsync,
:journal,
:pool_size,
:readpreference,
:replicaset,
:safe,
:slaveok,
:sockettimeoutms,
:ssl,
:safe,
:w,
:wtimeout,
:fsync,
:journal,
:connecttimeoutms,
:sockettimeoutms,
:wtimeoutms,
:pool_size
:wtimeoutms
]

OPT_VALID = {:connect => lambda {|arg| ['direct', 'replicaset', 'true', 'false', true, false].include?(arg)},
:replicaset => lambda {|arg| arg.length > 0},
:slaveok => lambda {|arg| ['true', 'false'].include?(arg)},
:ssl => lambda {|arg| ['true', 'false'].include?(arg)},
:safe => lambda {|arg| ['true', 'false'].include?(arg)},
:w => lambda {|arg| arg =~ /^\w+$/ },
:wtimeout => lambda {|arg| arg =~ /^\d+$/ },
:fsync => lambda {|arg| ['true', 'false'].include?(arg)},
:journal => lambda {|arg| ['true', 'false'].include?(arg)},
:connecttimeoutms => lambda {|arg| arg =~ /^\d+$/ },
:sockettimeoutms => lambda {|arg| arg =~ /^\d+$/ },
:wtimeoutms => lambda {|arg| arg =~ /^\d+$/ },
:pool_size => lambda {|arg| arg.to_i > 0 }
}

OPT_ERR = {:connect => "must be 'direct', 'replicaset', 'true', or 'false'",
:replicaset => "must be a string containing the name of the replica set to connect to",
:slaveok => "must be 'true' or 'false'",
:ssl => "must be 'true' or 'false'",
:safe => "must be 'true' or 'false'",
:w => "must be an integer indicating number of nodes to replicate to or a string specifying
that replication is required to the majority or nodes with a particilar getLastErrorMode.",
:wtimeout => "must be an integer specifying milliseconds",
:fsync => "must be 'true' or 'false'",
:journal => "must be 'true' or 'false'",
:connecttimeoutms => "must be an integer specifying milliseconds",
:sockettimeoutms => "must be an integer specifying milliseconds",
:wtimeoutms => "must be an integer specifying milliseconds",
:pool_size => "must be an integer greater than zero"
}

OPT_CONV = {:connect => lambda {|arg| arg == 'false' ? false : arg}, # be sure to convert 'false' to FalseClass
:replicaset => lambda {|arg| arg},
:slaveok => lambda {|arg| arg == 'true' ? true : false},
:ssl => lambda {|arg| arg == 'true' ? true : false},
:safe => lambda {|arg| arg == 'true' ? true : false},
:w => lambda {|arg| Mongo::Support.is_i?(arg) ? arg.to_i : arg.to_sym },
:wtimeout => lambda {|arg| arg.to_i},
:fsync => lambda {|arg| arg == 'true' ? true : false},
:journal => lambda {|arg| arg == 'true' ? true : false},
:connecttimeoutms => lambda {|arg| arg.to_f / 1000 }, # stored as seconds
:sockettimeoutms => lambda {|arg| arg.to_f / 1000 }, # stored as seconds
:wtimeoutms => lambda {|arg| arg.to_i },
:pool_size => lambda {|arg| arg.to_i }
}
OPT_VALID = {
:connect => lambda { |arg| [ 'direct', 'replicaset', 'true', 'false', true, false ].include?(arg) },
:connecttimeoutms => lambda { |arg| arg =~ /^\d+$/ },
:fsync => lambda { |arg| ['true', 'false'].include?(arg) },
:journal => lambda { |arg| ['true', 'false'].include?(arg) },
:pool_size => lambda { |arg| arg.to_i > 0 },
:readpreference => lambda { |arg| READ_PREFERENCES.keys.include?(arg) },
:replicaset => lambda { |arg| arg.length > 0 },
:safe => lambda { |arg| ['true', 'false'].include?(arg) },
:slaveok => lambda { |arg| ['true', 'false'].include?(arg) },
:sockettimeoutms => lambda { |arg| arg =~ /^\d+$/ },
:ssl => lambda { |arg| ['true', 'false'].include?(arg) },
:w => lambda { |arg| arg =~ /^\w+$/ },
:wtimeout => lambda { |arg| arg =~ /^\d+$/ },
:wtimeoutms => lambda { |arg| arg =~ /^\d+$/ }
}

OPT_ERR = {
:connect => "must be 'direct', 'replicaset', 'true', or 'false'",
:connecttimeoutms => "must be an integer specifying milliseconds",
:fsync => "must be 'true' or 'false'",
:journal => "must be 'true' or 'false'",
:pool_size => "must be an integer greater than zero",
:readpreference => "must be on of #{READ_PREFERENCES.keys.map(&:inspect).join(",")}",
:replicaset => "must be a string containing the name of the replica set to connect to",
:safe => "must be 'true' or 'false'",
:slaveok => "must be 'true' or 'false'",
:sockettimeoutms => "must be an integer specifying milliseconds",
:ssl => "must be 'true' or 'false'",
:w => "must be an integer indicating number of nodes to replicate to or a string " +
"specifying that replication is required to the majority or nodes with a " +
"particilar getLastErrorMode.",
:wtimeout => "must be an integer specifying milliseconds",
:wtimeoutms => "must be an integer specifying milliseconds"
}

OPT_CONV = {
:connect => lambda { |arg| arg == 'false' ? false : arg }, # convert 'false' to FalseClass
:connecttimeoutms => lambda { |arg| arg.to_f / 1000 }, # stored as seconds
:fsync => lambda { |arg| arg == 'true' ? true : false },
:journal => lambda { |arg| arg == 'true' ? true : false },
:pool_size => lambda { |arg| arg.to_i },
:readpreference => lambda { |arg| READ_PREFERENCES[arg] },
:replicaset => lambda { |arg| arg },
:safe => lambda { |arg| arg == 'true' ? true : false },
:slaveok => lambda { |arg| arg == 'true' ? true : false },
:sockettimeoutms => lambda { |arg| arg.to_f / 1000 }, # stored as seconds
:ssl => lambda { |arg| arg == 'true' ? true : false },
:w => lambda { |arg| Mongo::Support.is_i?(arg) ? arg.to_i : arg.to_sym },
:wtimeout => lambda { |arg| arg.to_i },
:wtimeoutms => lambda { |arg| arg.to_i }
}

attr_reader :auths,
:connect,
:connecttimeoutms,
:fsync,
:journal,
:nodes,
:pool_size,
:readpreference,
:replicaset,
:safe,
:slaveok,
:sockettimeoutms,
:ssl,
:safe,
:w,
:wtimeout,
:fsync,
:journal,
:connecttimeoutms,
:sockettimeoutms,
:wtimeoutms,
:pool_size,
:nodes
:wtimeoutms

# Parse a MongoDB URI. This method is used by MongoClient.from_uri.
# Returns an array of nodes and an array of db authorizations, if applicable.
Expand Down Expand Up @@ -201,7 +218,11 @@ def connection_options
opts[:pool_size] = @pool_size
end

if @slaveok
if @readpreference && replicaset?
opts[:read] = @readpreference
end

if @slaveok && !@readpreference
if direct?
opts[:slave_ok] = true
else
Expand Down
46 changes: 46 additions & 0 deletions test/functional/uri_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -128,4 +128,50 @@ def test_case_insensitivity
assert_equal true, parser.journal
assert_equal true, parser.safe
end

def test_read_preference_option_primary
parser = Mongo::URIParser.new("mongodb://localhost:27018?readPreference=primary")
assert_equal :primary, parser.readpreference
end

def test_read_preference_option_primary_preferred
parser = Mongo::URIParser.new("mongodb://localhost:27018?readPreference=primaryPreferred")
assert_equal :primary_preferred, parser.readpreference
end

def test_read_preference_option_secondary
parser = Mongo::URIParser.new("mongodb://localhost:27018?readPreference=secondary")
assert_equal :secondary, parser.readpreference
end

def test_read_preference_option_secondary_preferred
parser = Mongo::URIParser.new("mongodb://localhost:27018?readPreference=secondaryPreferred")
assert_equal :secondary_preferred, parser.readpreference
end

def test_read_preference_option_nearest
parser = Mongo::URIParser.new("mongodb://localhost:27018?readPreference=nearest")
assert_equal :nearest, parser.readpreference
end

def test_read_preference_option_with_invalid
assert_raise_error MongoArgumentError do
Mongo::URIParser.new("mongodb://localhost:27018?readPreference=invalid")
end
end

def test_read_preference_connection_options
parser = Mongo::URIParser.new("mongodb://localhost:27018?replicaset=test&readPreference=nearest")
assert_equal :nearest, parser.connection_options[:read]
end

def test_read_preference_connection_options_prefers_preference_over_slaveok
parser = Mongo::URIParser.new("mongodb://localhost:27018?replicaset=test&readPreference=nearest&slaveok=true")
assert_equal :nearest, parser.connection_options[:read]
end

def test_read_preference_when_no_replica_set
parser = Mongo::URIParser.new("mongodb://localhost:27018?readPreference=nearest")
assert_nil parser.connection_options[:read]
end
end

0 comments on commit 56c2724

Please sign in to comment.