From 56c2724e2cebdad27b710dd78e78b934a14bb3b3 Mon Sep 17 00:00:00 2001 From: Durran Jordan Date: Sun, 10 Feb 2013 12:00:56 +0100 Subject: [PATCH] Support read preferences in URI parser. 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 ] --- lib/mongo/util/uri_parser.rb | 143 ++++++++++++++++++++--------------- test/functional/uri_test.rb | 46 +++++++++++ 2 files changed, 128 insertions(+), 61 deletions(-) diff --git a/lib/mongo/util/uri_parser.rb b/lib/mongo/util/uri_parser.rb index 5e627b37c3..5da91d1126 100644 --- a/lib/mongo/util/uri_parser.rb +++ b/lib/mongo/util/uri_parser.rb @@ -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. @@ -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 diff --git a/test/functional/uri_test.rb b/test/functional/uri_test.rb index 26284cb289..4628257c5d 100644 --- a/test/functional/uri_test.rb +++ b/test/functional/uri_test.rb @@ -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