Added option to disable cursor timeout on Collection#find when #find is invoked with a block.

This commit is contained in:
Kyle Banker 2009-10-14 14:38:44 -04:00
parent 5ea585a475
commit c6d4150a51
7 changed files with 129 additions and 9 deletions

View File

@ -100,6 +100,13 @@ module Mongo
# objects missed, which were preset at both the start and # objects missed, which were preset at both the start and
# end of the query's execution. For details see # end of the query's execution. For details see
# http://www.mongodb.org/display/DOCS/How+to+do+Snapshotting+in+the+Mongo+Database # http://www.mongodb.org/display/DOCS/How+to+do+Snapshotting+in+the+Mongo+Database
# :timeout :: When +true+ (default), the returned cursor will be subject to
# the normal cursor timeout behavior of the mongod process.
# When +false+, the returned cursor will never timeout. Note
# that disabling timeout will only work when #find is invoked
# with a block. This is to prevent any inadvertant failure to
# close the cursor, as the cursor is explicitly closed when
# block code finishes.
def find(selector={}, options={}) def find(selector={}, options={})
fields = options.delete(:fields) fields = options.delete(:fields)
fields = ["_id"] if fields && fields.empty? fields = ["_id"] if fields && fields.empty?
@ -112,6 +119,10 @@ module Mongo
sort = options.delete(:sort) sort = options.delete(:sort)
hint = options.delete(:hint) hint = options.delete(:hint)
snapshot = options.delete(:snapshot) snapshot = options.delete(:snapshot)
if options[:timeout] == false && !block_given?
raise ArgumentError, "Timeout can be set to false only when #find is invoked with a block."
end
timeout = block_given? ? (options.delete(:timeout) || true) : true
if hint if hint
hint = normalize_hint_fields(hint) hint = normalize_hint_fields(hint)
else else
@ -119,7 +130,7 @@ module Mongo
end end
raise RuntimeError, "Unknown options [#{options.inspect}]" unless options.empty? raise RuntimeError, "Unknown options [#{options.inspect}]" unless options.empty?
cursor = @db.query(self, Query.new(selector, fields, skip, limit, sort, hint, snapshot)) cursor = @db.query(self, Query.new(selector, fields, skip, limit, sort, hint, snapshot, timeout))
if block_given? if block_given?
yield cursor yield cursor
cursor.close() cursor.close()

View File

@ -24,4 +24,6 @@ module Mongo
OP_GET_MORE = 2005 OP_GET_MORE = 2005
OP_DELETE = 2006 OP_DELETE = 2006
OP_KILL_CURSORS = 2007 OP_KILL_CURSORS = 2007
OP_QUERY_NO_CURSOR_TIMEOUT = 16
end end

View File

@ -29,7 +29,7 @@ module Mongo
super(OP_QUERY) super(OP_QUERY)
@query = query @query = query
@collection_name = collection_name @collection_name = collection_name
write_int(0) write_int(@query.query_opts)
write_string("#{db_name}.#{collection_name}") write_string("#{db_name}.#{collection_name}")
write_int(query.number_to_skip) write_int(query.number_to_skip)
write_int(query.number_to_return) write_int(query.number_to_return)

View File

@ -19,8 +19,6 @@ require 'mongo/message'
require 'mongo/types/code' require 'mongo/types/code'
module Mongo module Mongo
# A query against a collection. A query's selector is a hash. See the
# Mongo documentation for query details. # Mongo documentation for query details.
class Query class Query
@ -56,13 +54,22 @@ module Mongo
# probably will not be what you intend because key order # probably will not be what you intend because key order
# is not preserved. (order_by is called :sort in calls to # is not preserved. (order_by is called :sort in calls to
# Collection#find.) # Collection#find.)
# :snapshot :: If true, snapshot mode will be used for this query.
# Snapshot mode assures no duplicates are returned, or
# objects missed, which were preset at both the start and
# end of the query's execution. For details see
# http://www.mongodb.org/display/DOCS/How+to+do+Snapshotting+in+the+Mongo+Database
# #
# hint :: If not +nil+, specifies query hint fields. Must be either # hint :: If not +nil+, specifies query hint fields. Must be either
# +nil+ or a hash (preferably an OrderedHash). See # +nil+ or a hash (preferably an OrderedHash). See Collection#hint.
# Collection#hint. #
def initialize(sel={}, return_fields=nil, number_to_skip=0, number_to_return=0, order_by=nil, hint=nil, snapshot=nil) # timeout :: When +true+ (default), the returned cursor will be subject to
@number_to_skip, @number_to_return, @order_by, @hint, @snapshot = # the normal cursor timeout behavior of the mongod process.
number_to_skip, number_to_return, order_by, hint, snapshot # When +false+, the returned cursor will never timeout. Care should
# be taken to ensure that cursors with timeout disabled are properly closed.
def initialize(sel={}, return_fields=nil, number_to_skip=0, number_to_return=0, order_by=nil, hint=nil, snapshot=nil, timeout=true)
@number_to_skip, @number_to_return, @order_by, @hint, @snapshot, @timeout =
number_to_skip, number_to_return, order_by, hint, snapshot, timeout
@explain = nil @explain = nil
self.selector = sel self.selector = sel
self.fields = return_fields self.fields = return_fields
@ -111,6 +118,12 @@ module Mongo
@order_by || @explain || @hint || @snapshot @order_by || @explain || @hint || @snapshot
end end
# Returns an integer indicating which query options have been selected.
# See http://www.mongodb.org/display/DOCS/Mongo+Wire+Protocol#MongoWireProtocol-OPQUERY
def query_opts
@timeout ? 0 : OP_QUERY_NO_CURSOR_TIMEOUT
end
def to_s def to_s
"find(#{@selector.inspect})" + (@order_by ? ".sort(#{@order_by.inspect})" : "") "find(#{@selector.inspect})" + (@order_by ? ".sort(#{@order_by.inspect})" : "")
end end

View File

@ -0,0 +1,40 @@
# --
# Copyright (C) 2008-2009 10gen Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ++
$LOAD_PATH[0,0] = File.join(File.dirname(__FILE__), '..', 'lib')
require 'mongo'
require 'test/unit'
class TestQueryMessage < Test::Unit::TestCase
include Mongo
def test_timeout_opcodes
@timeout = true
@query = Query.new({}, nil, 0, 0, nil, nil, nil, @timeout)
@query_message = QueryMessage.new('db', 'collection', @query)
buf = @query_message.buf.instance_variable_get(:@buf)
assert_equal 0, buf[16]
@timeout = false
@query = Query.new({}, nil, 0, 0, nil, nil, nil, @timeout)
@query_message = QueryMessage.new('db', 'collection', @query)
buf = @query_message.buf.instance_variable_get(:@buf)
assert_equal 16, buf[16]
end
end

View File

@ -123,6 +123,24 @@ class TestCollection < Test::Unit::TestCase
assert_equal @@test.size, @@test.count assert_equal @@test.size, @@test.count
end end
def test_no_timeout_option
@@test.drop
assert_raise ArgumentError, "Timeout can be set to false only when #find is invoked with a block." do
@@test.find({}, :timeout => false)
end
@@test.find({}, :timeout => false) do |cursor|
assert_equal 0, cursor.count
end
@@test.save("x" => 1)
@@test.save("x" => 2)
@@test.find({}, :timeout => false) do |cursor|
assert_equal 2, cursor.count
end
end
def test_find_one def test_find_one
id = @@test.save("hello" => "world", "foo" => "bar") id = @@test.save("hello" => "world", "foo" => "bar")

36
test/test_query.rb Normal file
View File

@ -0,0 +1,36 @@
# --
# Copyright (C) 2008-2009 10gen Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ++
$LOAD_PATH[0,0] = File.join(File.dirname(__FILE__), '..', 'lib')
require 'mongo'
require 'test/unit'
class TestQuery < Test::Unit::TestCase
include Mongo
def test_timeout_opcodes
@timeout = true
@query = Query.new({}, nil, 0, 0, nil, nil, nil, @timeout)
assert_equal 0, @query.query_opts
@timeout = false
@query = Query.new({}, nil, 0, 0, nil, nil, nil, @timeout)
assert_equal 16, @query.query_opts
end
end