From 50c38c6c6b74fdb9a4adeca80fc903c299a527c8 Mon Sep 17 00:00:00 2001 From: Kyle Banker Date: Tue, 6 Sep 2011 14:58:03 -0400 Subject: [PATCH] RUBY-284 document :read API --- lib/mongo/collection.rb | 18 +++++++- lib/mongo/connection.rb | 1 + lib/mongo/cursor.rb | 8 +++- lib/mongo/db.rb | 8 +++- lib/mongo/repl_set_connection.rb | 7 ++- lib/mongo/util/support.rb | 10 ++++- test/replica_sets/read_preference_test.rb | 54 ++++++++++++----------- test/unit/read_test.rb | 11 +++-- 8 files changed, 78 insertions(+), 39 deletions(-) diff --git a/lib/mongo/collection.rb b/lib/mongo/collection.rb index 4411458..33d330b 100644 --- a/lib/mongo/collection.rb +++ b/lib/mongo/collection.rb @@ -34,6 +34,11 @@ module Mongo # for insert, update, and remove method called on this Collection instance. If no # value is provided, the default value set on this instance's DB will be used. This # default can be overridden for any invocation of insert, update, or remove. + # @option options [:primary, :secondary] :read The default read preference for queries + # initiates from this connection object. If +:secondary+ is chosen, reads will be sent + # to one of the closest available secondary nodes. If a secondary node cannot be located, the + # read will be sent to the primary. If this option is left unspecified, the value of the read + # preference for this collection's associated Mongo::DB object will be used. # # @raise [InvalidNSName] # if collection name is empty, contains '$', or starts or ends with '.' @@ -84,8 +89,12 @@ module Mongo @cache = Hash.new(0) unless pk_factory @safe = opts.fetch(:safe, @db.safe) - @read = opts.fetch(:read, @db.read_preference) - @read_preference = @read.is_a?(Hash) ? @read.dup : @read + if value = opts[:read] + Mongo::Support.validate_read_preference(value) + else + value = @db.read_preference + end + @read_preference = value.is_a?(Hash) ? value.dup : value end @pk_factory = pk_factory || opts[:pk] || BSON::ObjectId @hint = nil @@ -157,6 +166,11 @@ module Mongo # you can cut down on network traffic and decoding time. If using a Hash, keys should be field # names and values should be either 1 or 0, depending on whether you want to include or exclude # the given field. + # @option opts [:primary, :secondary] :read The default read preference for queries + # initiates from this connection object. If +:secondary+ is chosen, reads will be sent + # to one of the closest available secondary nodes. If a secondary node cannot be located, the + # read will be sent to the primary. If this option is left unspecified, the value of the read + # preference for this Collection object will be used. # @option opts [Integer] :skip number of documents to skip from the beginning of the result set # @option opts [Integer] :limit maximum number of documents to return # @option opts [Array] :sort an array of [key, direction] pairs to sort by. Direction should diff --git a/lib/mongo/connection.rb b/lib/mongo/connection.rb index 4b5b4f8..f17abe9 100644 --- a/lib/mongo/connection.rb +++ b/lib/mongo/connection.rb @@ -685,6 +685,7 @@ module Mongo @connect_timeout = opts[:connect_timeout] || nil # Mutex for synchronizing pool access + # TODO: remove this. @connection_mutex = Mutex.new # Global safe option. This is false by default. diff --git a/lib/mongo/cursor.rb b/lib/mongo/cursor.rb index 971c418..f592759 100644 --- a/lib/mongo/cursor.rb +++ b/lib/mongo/cursor.rb @@ -70,8 +70,12 @@ module Mongo @query_run = false @transformer = opts[:transformer] - read = opts[:read] || collection.read_preference - @read_preference = read.is_a?(Hash) ? read.dup : read + if value = opts[:read] + Mongo::Support.validate_read_preference(value) + else + value = collection.read_preference + end + @read_preference = value.is_a?(Hash) ? value.dup : value batch_size(opts[:batch_size] || 0) @full_collection_name = "#{@collection.db.name}.#{@collection.name}" diff --git a/lib/mongo/db.rb b/lib/mongo/db.rb index b25ecb9..e82c2f7 100644 --- a/lib/mongo/db.rb +++ b/lib/mongo/db.rb @@ -82,8 +82,12 @@ module Mongo @strict = opts[:strict] @pk_factory = opts[:pk] @safe = opts.fetch(:safe, @connection.safe) - read = opts.fetch(:read, @connection.read_preference) - @read_preference = read.is_a?(Hash) ? read.dup : read + if value = opts[:read] + Mongo::Support.validate_read_preference(value) + else + value = @connection.read_preference + end + @read_preference = value.is_a?(Hash) ? value.dup : value @cache_time = opts[:cache_time] || 300 #5 minutes. end diff --git a/lib/mongo/repl_set_connection.rb b/lib/mongo/repl_set_connection.rb index f016d13..ef7f50f 100644 --- a/lib/mongo/repl_set_connection.rb +++ b/lib/mongo/repl_set_connection.rb @@ -41,8 +41,10 @@ module Mongo # propogated to DB objects instantiated off of this Connection. This # default can be overridden upon instantiation of any DB by explicity setting a :safe value # on initialization. - # @option options [Boolean] :read_secondary(false) If true, a random secondary node will be chosen, - # and all reads will be directed to that node. + # @option options [:primary, :secondary] :read (:primary) The default read preference for Mongo::DB + # objects created from this connection object. If +:secondary+ is chosen, reads will be sent + # to one of the closest available secondary nodes. If a secondary node cannot be located, the + # read will be sent to the primary. # @option options [Logger, #debug] :logger (nil) Logger instance to receive driver operation log. # @option options [Integer] :pool_size (1) The maximum number of socket connections allowed per # connection pool. Note: this setting is relevant only for multi-threaded applications. @@ -125,6 +127,7 @@ module Mongo @read = :secondary else @read = opts.fetch(:read, :primary) + Mongo::Support.validate_read_preference(@read) end @connected = false diff --git a/lib/mongo/util/support.rb b/lib/mongo/util/support.rb index 2f56b18..7a63643 100644 --- a/lib/mongo/util/support.rb +++ b/lib/mongo/util/support.rb @@ -44,7 +44,6 @@ module Mongo Digest::MD5.hexdigest("#{username}:mongo:#{plaintext}") end - def validate_db_name(db_name) unless [String, Symbol].include?(db_name.class) raise TypeError, "db_name must be a string or symbol" @@ -59,6 +58,15 @@ module Mongo db_name end + def validate_read_preference(value) + if [:primary, :secondary, nil].include?(value) + return true + else + raise MongoArgumentError, "#{value} is not a valid read preference. " + + "Please specify either :primary or :secondary." + end + end + def format_order_clause(order) case order when String, Symbol then string_as_sort_parameters(order) diff --git a/test/replica_sets/read_preference_test.rb b/test/replica_sets/read_preference_test.rb index 59952e9..59a9b3c 100644 --- a/test/replica_sets/read_preference_test.rb +++ b/test/replica_sets/read_preference_test.rb @@ -1,41 +1,43 @@ $:.unshift(File.join(File.dirname(__FILE__), '..', 'lib')) require './test/replica_sets/rs_test_helper' +# TODO: enable this once we enable reads from tags. class ReadPreferenceTest < Test::Unit::TestCase include Mongo - def setup - @conn = ReplSetConnection.new([RS.host, RS.ports[0], RS.host, RS.ports[1]], :read => :secondary, :pool_size => 50) - @db = @conn.db(MONGO_TEST_DB) - @db.drop_collection("test-sets") - end + #def setup + # @conn = ReplSetConnection.new([RS.host, RS.ports[0], RS.host, RS.ports[1]], :read => :secondary, :pool_size => 50) + # @db = @conn.db(MONGO_TEST_DB) + # @db.drop_collection("test-sets") + #end - def test_query_tagged - col = @db['mongo-test'] + # TODO: enable this once we enable reads from tags. + # def test_query_tagged + # col = @db['mongo-test'] - col.insert({:a => 1}, :safe => {:w => 3}) - col.find_one({}, :read => {:db => "main"}) - col.find_one({}, :read => {:dc => "ny"}) - col.find_one({}, :read => {:dc => "sf"}) + # col.insert({:a => 1}, :safe => {:w => 3}) + # col.find_one({}, :read => {:db => "main"}) + # col.find_one({}, :read => {:dc => "ny"}) + # col.find_one({}, :read => {:dc => "sf"}) - assert_raise Mongo::NodeWithTagsNotFound do - col.find_one({}, :read => {:foo => "bar"}) - end + # assert_raise Mongo::NodeWithTagsNotFound do + # col.find_one({}, :read => {:foo => "bar"}) + # end - threads = [] - 100.times do - threads << Thread.new do - col.find_one({}, :read => {:dc => "sf"}) - end - end + # threads = [] + # 100.times do + # threads << Thread.new do + # col.find_one({}, :read => {:dc => "sf"}) + # end + # end - threads.each {|t| t.join } + # threads.each {|t| t.join } - col.remove - end + # col.remove + # end - def teardown - RS.restart_killed_nodes - end + #def teardown + # RS.restart_killed_nodes + #end end diff --git a/test/unit/read_test.rb b/test/unit/read_test.rb index 40ce098..0a34668 100644 --- a/test/unit/read_test.rb +++ b/test/unit/read_test.rb @@ -87,11 +87,14 @@ class ReadTest < Test::Unit::TestCase end should "allow override alternate value on query" do - @con.expects(:receive_message).with do |o, m, l, s, c, r| - tags = {:dc => "ny"} - end.returns([[], 0, 0]) + # TODO: enable this test once we enable reading from tags. + # @con.expects(:receive_message).with do |o, m, l, s, c, r| + # tags = {:dc => "ny"} + # end.returns([[], 0, 0]) - @col.find_one({:a => 1}, :read => {:dc => "ny"}) + assert_raise MongoArgumentError do + @col.find_one({:a => 1}, :read => {:dc => "ny"}) + end end end end