Added Cursor#explain. Made query sends lazy.
This commit is contained in:
parent
dd48ec13ab
commit
1afd968f5d
|
@ -29,14 +29,19 @@ module XGen
|
|||
|
||||
RESPONSE_HEADER_SIZE = 20
|
||||
|
||||
def initialize(db, collection, num_to_return=0)
|
||||
@db, @collection, @num_to_return = db, collection, num_to_return
|
||||
attr_reader :db, :collection, :query_message
|
||||
|
||||
def initialize(db, collection, query_message)
|
||||
@db, @collection, @query_message = db, collection, query_message
|
||||
@num_to_return = query_message.query.number_to_return || 0
|
||||
@cache = []
|
||||
@closed = false
|
||||
@can_call_to_a = true
|
||||
read_all
|
||||
@query_run = false
|
||||
end
|
||||
|
||||
def closed?; @closed; end
|
||||
|
||||
# Return +true+ if there are more records to retrieve. We do not check
|
||||
# @num_to_return; #each is responsible for doing that.
|
||||
def more?
|
||||
|
@ -100,9 +105,20 @@ module XGen
|
|||
@rows
|
||||
end
|
||||
|
||||
# Returns an explain plan record.
|
||||
def explain
|
||||
sel = OrderedHash.new
|
||||
sel['query'] = @query_message.query.selector
|
||||
sel['$explain'] = true
|
||||
c = Cursor.new(@db, @collection, QueryMessage.new(@db.name, @collection, Query.new(sel)))
|
||||
e = c.next_object
|
||||
c.close
|
||||
e
|
||||
end
|
||||
|
||||
# Close the cursor.
|
||||
def close
|
||||
@db.send_to_db(KillCursorMessage(@cursor_id)) if @cursor_id
|
||||
@db.send_to_db(KillCursorsMessage.new(@cursor_id)) if @cursor_id
|
||||
@cache = []
|
||||
@cursor_id = 0
|
||||
@closed = true
|
||||
|
@ -146,6 +162,7 @@ module XGen
|
|||
private
|
||||
|
||||
def next_object_on_wire
|
||||
send_query_if_needed
|
||||
# if @n_remaining is 0 but we have a non-zero cursor, there are more
|
||||
# to fetch, so do a GetMore operation, but don't do it here - do it
|
||||
# when someone pulls an object out of the cache and it's empty
|
||||
|
@ -154,6 +171,7 @@ module XGen
|
|||
end
|
||||
|
||||
def refill_via_get_more
|
||||
send_query_if_needed
|
||||
return if @cursor_id == 0
|
||||
@db.send_to_db(GetMoreMessage.new(@db.name, @collection, @cursor_id))
|
||||
read_all
|
||||
|
@ -170,6 +188,15 @@ module XGen
|
|||
BSON.new(@db).deserialize(buf)
|
||||
end
|
||||
|
||||
def send_query_if_needed
|
||||
# Run query first time we request an object from the wire
|
||||
unless @query_run
|
||||
@db.send_query_message(@query_message)
|
||||
@query_run = true
|
||||
read_all
|
||||
end
|
||||
end
|
||||
|
||||
def to_s
|
||||
"DBResponse(flags=#@result_flags, cursor_id=#@cursor_id, start=#@starting_from, n_returned=#@n_returned)"
|
||||
end
|
||||
|
@ -177,4 +204,3 @@ module XGen
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -177,12 +177,19 @@ module XGen
|
|||
send_to_db(MsgMessage.new(msg))
|
||||
end
|
||||
|
||||
# Send a Query to +collection_name+ and return a Cursor over the
|
||||
# results.
|
||||
# Returns a Cursor over the query results.
|
||||
#
|
||||
# Note that the query gets sent lazily; the cursor calls
|
||||
# #send_query_message when needed. If the caller never requests an
|
||||
# object from the cursor, the query never gets sent.
|
||||
def query(collection_name, query)
|
||||
Cursor.new(self, collection_name, QueryMessage.new(@name, collection_name, query))
|
||||
end
|
||||
|
||||
# Used by a Cursor to lazily send the query to the database.
|
||||
def send_query_message(query_message)
|
||||
@semaphore.synchronize {
|
||||
send_to_db(QueryMessage.new(@name, collection_name, query))
|
||||
Cursor.new(self, collection_name, query.number_to_return)
|
||||
send_to_db(query_message)
|
||||
}
|
||||
end
|
||||
|
||||
|
|
|
@ -8,8 +8,11 @@ module XGen
|
|||
|
||||
class QueryMessage < Message
|
||||
|
||||
attr_reader :query
|
||||
|
||||
def initialize(db_name, collection_name, query)
|
||||
super(OP_QUERY)
|
||||
@query = query
|
||||
write_int(0)
|
||||
write_string("#{db_name}.#{collection_name}")
|
||||
write_int(query.number_to_skip)
|
||||
|
|
|
@ -0,0 +1,54 @@
|
|||
$LOAD_PATH[0,0] = File.join(File.dirname(__FILE__), '..', 'lib')
|
||||
require 'mongo'
|
||||
require 'test/unit'
|
||||
|
||||
# NOTE: assumes Mongo is running
|
||||
class CursorTest < Test::Unit::TestCase
|
||||
|
||||
include XGen::Mongo::Driver
|
||||
|
||||
def setup
|
||||
host = ENV['MONGO_RUBY_DRIVER_HOST'] || 'localhost'
|
||||
port = ENV['MONGO_RUBY_DRIVER_PORT'] || Mongo::DEFAULT_PORT
|
||||
@db = Mongo.new(host, port).db('ruby-mongo-test')
|
||||
@coll = @db.collection('test')
|
||||
@coll.clear
|
||||
@r1 = @coll.insert('a' => 1) # collection not created until it's used
|
||||
@coll_full_name = 'ruby-mongo-test.test'
|
||||
end
|
||||
|
||||
def teardown
|
||||
@coll.clear unless @coll == nil || @db.socket.closed?
|
||||
end
|
||||
|
||||
def test_explain
|
||||
cursor = @coll.find('a' => 1)
|
||||
explaination = cursor.explain
|
||||
assert_not_nil explaination['cursor']
|
||||
assert_kind_of Numeric, explaination['n']
|
||||
assert_kind_of Numeric, explaination['millis']
|
||||
assert_kind_of Numeric, explaination['nscanned']
|
||||
end
|
||||
|
||||
def test_close_no_query_sent
|
||||
begin
|
||||
cursor = @coll.find('a' => 1)
|
||||
cursor.close
|
||||
assert cursor.closed?
|
||||
rescue => ex
|
||||
fail ex.to_s
|
||||
end
|
||||
end
|
||||
|
||||
def test_close_after_query_sent
|
||||
begin
|
||||
cursor = @coll.find('a' => 1)
|
||||
cursor.next_object
|
||||
cursor.close
|
||||
assert cursor.closed?
|
||||
rescue => ex
|
||||
fail ex.to_s
|
||||
end
|
||||
end
|
||||
|
||||
end
|
Loading…
Reference in New Issue