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
|
RESPONSE_HEADER_SIZE = 20
|
||||||
|
|
||||||
def initialize(db, collection, num_to_return=0)
|
attr_reader :db, :collection, :query_message
|
||||||
@db, @collection, @num_to_return = db, collection, num_to_return
|
|
||||||
|
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 = []
|
@cache = []
|
||||||
@closed = false
|
@closed = false
|
||||||
@can_call_to_a = true
|
@can_call_to_a = true
|
||||||
read_all
|
@query_run = false
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def closed?; @closed; end
|
||||||
|
|
||||||
# Return +true+ if there are more records to retrieve. We do not check
|
# Return +true+ if there are more records to retrieve. We do not check
|
||||||
# @num_to_return; #each is responsible for doing that.
|
# @num_to_return; #each is responsible for doing that.
|
||||||
def more?
|
def more?
|
||||||
|
@ -100,9 +105,20 @@ module XGen
|
||||||
@rows
|
@rows
|
||||||
end
|
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.
|
# Close the cursor.
|
||||||
def close
|
def close
|
||||||
@db.send_to_db(KillCursorMessage(@cursor_id)) if @cursor_id
|
@db.send_to_db(KillCursorsMessage.new(@cursor_id)) if @cursor_id
|
||||||
@cache = []
|
@cache = []
|
||||||
@cursor_id = 0
|
@cursor_id = 0
|
||||||
@closed = true
|
@closed = true
|
||||||
|
@ -146,6 +162,7 @@ module XGen
|
||||||
private
|
private
|
||||||
|
|
||||||
def next_object_on_wire
|
def next_object_on_wire
|
||||||
|
send_query_if_needed
|
||||||
# if @n_remaining is 0 but we have a non-zero cursor, there are more
|
# 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
|
# 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
|
# when someone pulls an object out of the cache and it's empty
|
||||||
|
@ -154,6 +171,7 @@ module XGen
|
||||||
end
|
end
|
||||||
|
|
||||||
def refill_via_get_more
|
def refill_via_get_more
|
||||||
|
send_query_if_needed
|
||||||
return if @cursor_id == 0
|
return if @cursor_id == 0
|
||||||
@db.send_to_db(GetMoreMessage.new(@db.name, @collection, @cursor_id))
|
@db.send_to_db(GetMoreMessage.new(@db.name, @collection, @cursor_id))
|
||||||
read_all
|
read_all
|
||||||
|
@ -170,6 +188,15 @@ module XGen
|
||||||
BSON.new(@db).deserialize(buf)
|
BSON.new(@db).deserialize(buf)
|
||||||
end
|
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
|
def to_s
|
||||||
"DBResponse(flags=#@result_flags, cursor_id=#@cursor_id, start=#@starting_from, n_returned=#@n_returned)"
|
"DBResponse(flags=#@result_flags, cursor_id=#@cursor_id, start=#@starting_from, n_returned=#@n_returned)"
|
||||||
end
|
end
|
||||||
|
@ -177,4 +204,3 @@ module XGen
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -177,12 +177,19 @@ module XGen
|
||||||
send_to_db(MsgMessage.new(msg))
|
send_to_db(MsgMessage.new(msg))
|
||||||
end
|
end
|
||||||
|
|
||||||
# Send a Query to +collection_name+ and return a Cursor over the
|
# Returns a Cursor over the query results.
|
||||||
# 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)
|
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 {
|
@semaphore.synchronize {
|
||||||
send_to_db(QueryMessage.new(@name, collection_name, query))
|
send_to_db(query_message)
|
||||||
Cursor.new(self, collection_name, query.number_to_return)
|
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -8,8 +8,11 @@ module XGen
|
||||||
|
|
||||||
class QueryMessage < Message
|
class QueryMessage < Message
|
||||||
|
|
||||||
|
attr_reader :query
|
||||||
|
|
||||||
def initialize(db_name, collection_name, query)
|
def initialize(db_name, collection_name, query)
|
||||||
super(OP_QUERY)
|
super(OP_QUERY)
|
||||||
|
@query = query
|
||||||
write_int(0)
|
write_int(0)
|
||||||
write_string("#{db_name}.#{collection_name}")
|
write_string("#{db_name}.#{collection_name}")
|
||||||
write_int(query.number_to_skip)
|
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